我已经将这段代码作为一些课程作业的一部分编写,我们必须在不使用 find、du 或 -R 命令的情况下创建递归文件计数器。这似乎有效,但总是将 hiddenfilecounter 返回为 0。是否有任何特殊原因这不起作用。
#!/bin/sh
shopt -s dotglob
directoryCounter=0
fileCounter=0
hiddenDirectoryCounter=0
hiddenFileCounter=0
listAllFiles()
{
local dir=$1
local file
local bn=$(basename $dir)
for file in "$dir"/*; do
if [[ $bn == .* ]]; then
let hiddenDirectoryCounter+=1
listAllFiles "$file"
elif [[ -f $file && "$file" == ^. ]]; then
ls -l $file
let hiddenFileCounter+=1
elif [[ -f $file ]];then
ls -l $file
let fileCounter+=1
elif [[ -d $file ]]; then
listAllFiles "$file"
let directoryCounter+=1
fi
done
}
listAllFiles $1
echo File found: $fileCounter
echo Directories found: $directoryCounter
echo Hidden directories found: $hiddenDirectoryCounter
echo Hidden files found: $hiddenFileCounter
Run Code Online (Sandbox Code Playgroud)
这里有一些错误。首先,/bin/sh当您真正想要运行/bin/bash.
现在,检查隐藏文件的脚本部分是
elif [[ -f $file && "$file" == ^. ]]; then
Run Code Online (Sandbox Code Playgroud)
如果您将脚本运行为foo.sh /home/foo,则每个脚本都$file将以点开头,/home/foo因此即使隐藏文件也不会以点开头。此外,您不能使用^with ==,它无论如何都匹配开头(请参阅此处)。
无论如何,您的脚本的有效实现将是
#!/usr/bin/env bash
shopt -s dotglob
directoryCounter=0
fileCounter=0
hiddenDirectoryCounter=0
hiddenFileCounter=0
listAllFiles()
{
local dir=$1
local file
local bn="$(basename -- "$dir")"
for file in "$dir"/*; do
## Use ..* so you don't count the current dir (.) as hidden
if [[ $bn == .?* ]]; then
let hiddenDirectoryCounter+=1
listAllFiles "$file"
## Match only the filename, not the whole path
elif [[ -f $file && "$(basename "$file")" == .* ]]; then
let hiddenFileCounter+=1
elif [[ -f "$file" ]];then
let fileCounter+=1
elif [[ -d "$file" ]]; then
listAllFiles "$file"
let directoryCounter+=1
fi
done
}
listAllFiles $1
echo File found: $fileCounter
echo Directories found: $directoryCounter
echo Hidden directories found: $hiddenDirectoryCounter
echo Hidden files found: $hiddenFileCounter
Run Code Online (Sandbox Code Playgroud)
但是,这不是一个很好的方法,您不能将多个目录作为输入处理,更重要的是,您只检查是否$bn是隐藏目录,从不$file,这意味着您永远不会找到多个隐藏目录你让它变得不必要地复杂。您可以通过使用globstar. 我会这样写:
#!/usr/bin/env bash
shopt -s dotglob
## globstar causes ** to match all files and directories
## recursively.
shopt -s globstar
for dir in "$@"; do
for file in "$dir"/**; do
if [[ -f $file && "$(basename "$file")" == .* ]]; then
let hiddenFileCounter+=1
elif [[ -d $file && "$(basename "$file")" == .?* ]]; then
let hiddenDirectoryCounter+=1
elif [[ -f $file ]] ; then
let fileCounter+=1
elif [[ -d $file ]] ; then
let directoryCounter+=1
fi
done
done
echo Files found: $fileCounter
echo Directories found: $directoryCounter
echo Hidden directories found: $hiddenDirectoryCounter
echo Hidden files found: $hiddenFileCounter
Run Code Online (Sandbox Code Playgroud)
您的代码有几个问题:
如果您指定/bin/sh为 she-bang,那么您应该使用sh语法编写脚本(sh如果您打算移植到非常旧的系统,则应使用POSIX语法或 Bourne shell 语法)。没有shoptsinsh语法(shopts是bash特定的),也没有[[ ... ]](这ksh也是在zshand 中可用的构造bash),也没有local,也没有let。
基本上,您的脚本仅适用于bash,因此您应该使用#! /bin/bash -she-bang(但是您的脚本将无法在bash未安装的系统上运行),或者将其转换为sh语法。
在列表上下文中不加引号的变量,例如在命令的参数中(如basename $file)是 split+glob 运算符。你不想在这里这样做。
将任意数据传递给命令时,您必须使用--将参数与选项分开。否则以 开头的文件名将-被视为一个选项。所以:bn=$(basename -- "$file")
来自 Microsoft 一词的人存在混淆-d和-f常见问题。在 Unix 中,一切(大多数东西)都是一个文件。有许多不同类型的文件:目录、符号链接、常规文件、fifos、套接字、设备和许多其他文件,具体取决于实际的 Unix 变体。
[[ -d ... ]]如果文件是 directory 类型,[[ -f ... ]]如果文件是regular类型,则返回 true 。其他类型的文件呢?同样在符号链接的情况下,[[ -d ... ]]如果文件是符号链接类型,但最终解析为目录类型的文件,则返回 true 。这意味着,例如,如果有一个符号链接指向/那里,您的脚本将无限循环。
就好像使用find -L(以前,find -follow)一样,除了find -L能够检测循环。出于同样的原因,除非您的版本是 4.3 或更高版本,否则您不能使用bash's globstar(尽管您可以使用没有相同问题的zsh或ksh93等效的)bash。
那么,您是否要考虑目录与非目录文件?还是仅目录和常规文件,而忽略所有其他文件?
在第一种情况下,要测试目录,它是[ -d "$file" ] && [ ! -L "$file" ],而对于非目录:[ -L "$file" ] || [ ! -d "$file" ]。
在第二种情况下:[ -d "$file" ] && [ ! -L "$file" ]和[ -f "$file" ] && [ ! -L "$file" ]。
应该注意的是,您计算目录条目的次数多于文件数。例如,如果一个文件显示为两个不同的目录条目(两个硬链接),它将被计算两次。还有两个目录条目您没有计算:.和..(尽管您可能不想计算它们)。
该[命令或[[ ... ]]结构和外壳通配符时,他们无法访问文件(或缺乏许可或因为该文件不存在或路径组件不是目录)不报告错误,所以会有这样的情况,你会得到错误的数字,但不会收到一条错误消息告诉您原因。要解决此问题,您可以检查对目录的读取和搜索访问权限并相应地报告错误。
在[[ ... == pattern ]]操作者期望的文件名/通配符模式,而不是正则表达式。在 Bourne/POSIXsh语法中,您将case为此使用一个语句。通配符等效于^\.is .*,但在这里,您匹配完整路径,因此将是(作为正则表达式): /\.[^/]*$,除非您使用ksh扩展模式,否则您无法将其转换为通配符模式。
相反,您可以使用:
case ${fil##*/} in
(.*) ...
esac
Run Code Online (Sandbox Code Playgroud)
在 POSIXsh语法中。
因此,考虑到所有这些,您可以改为编写它(使用 POSIXsh语法):
hiddenDirs=0 hiddenNonDirs=0 dirs=0 nonDirs=0
isDir() {
[ -d "$1" ] && [ ! -L "$1" ]
}
isHidden() {
case ${1##*/} in
(.*) return 0
esac
return 1
}
listAllFiles() {
for f do
ls -ld -- "$f" || continue
if isDir "$f"; then
if isHidden "$f"; then
hiddenDirs=$(($hiddenDirs + 1))
else
dirs=$(($dirs + 1))
fi
if [ ! -r "$f" ]; then
printf >&2 'Error: %s directory not readable\n' "$f"
continue
fi
if [ ! -x "$f" ]; then
printf >&2 'Error: %s directory not searchable\n' "$f"
continue
fi
set -- "$f"/[*] "$f"/*
if [ "$#" -eq 2 ] && [ "$1" != "$2" ]; then
shift 2
else
shift
fi
n=$#
set -- "$f"/.[*] "$f"/.* "$@"
if [ "$(($# - $n))" -eq 2 ] && [ "$1" != "$2" ]; then
shift 2
else
shift
for f do
case ${f##*/} in
(.|..) ;;
(*) set -- "$@" "$f"
esac
shift
done
fi
listAllFiles "$@"
else
if isHidden "$f"; then
hiddenNonDirs=$(($hiddenNonDirs + 1))
else
nonDirs=$(($nonDirs + 1))
fi
fi
done
}
Run Code Online (Sandbox Code Playgroud)