我正在处理 bash 提示符 PS1,我想打印当前目录中的文件数量。
我编写了一个工作代码,但是,
有没有办法简化这个(冗余)脚本?
$(ls -l | grep ^- | wc -l) $(if [ $(ls -l | grep ^- | wc -l) -eq 1 ]; then echo "file"; else echo "files"; fi)
我的目的是打印文件夹中的文件数量和如果需要声明处理复数,管理两种情况:
- 1 个文件
- 2 个文件
例如我的 ~ 文件夹包含三个文件,因此脚本应该打印单词“files”;我的 ~/Desktop 只有一个文件,因此该脚本应该打印“文件”
我写下了上面的代码行,这使得工作得以完成,但我认为,有一种简洁且更智能的方法来实现它......
答案1
首先,您可以使用更快的命令来找到正确的数字。我的测试表明这比(大约 4:3)find . -maxdepth 1 -type f -not -name '.*' | wc -l
要快。ls -l | grep '^-' | wc -l
如果您还想要隐藏文件,则必须使用ls -a
或省略-not -name '.*'
查找部分。
接下来,您不需要对文件进行两次计数,而是将结果存储在变量中并重用它:
$(count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
if [[ "$count" -eq 1 ]]; then
echo "1 file"
else
echo "$count files"
fi)
正如您所看到的,您必须使用一个子 shell,否则该变量将不可用于第二个命令。我还使用了 bash 特定的[[ ... ]]
而不是[ ... ]
这里。一般来说,这是更好的解决方案。
最后,您还可以使用 Bash 变量在打印提示$PROMPT_COMMAND
之前执行一些代码。$PS1
这样您就不必将所有代码存储在$PS1
变量中。变量$count
将是全球的尽管。它可能看起来像这样:
function count_files () {
__count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
if [[ "$__count" -eq 1 ]]; then
__plural=
else
__plural=s
fi
}
PROMPT_COMMAND=count_files
PS1='other stuff $__count file$__plural more stuff'
但不确定您将其中哪一个步骤算作简化:)
编辑
我刚刚发现这个所以线程。它可以用来对文件进行计数,如下所示
function count_files () {
local name
__count=0
for name in *; do
[[ -f "$name" ]] && ((__count++))
done
# like above ...
}
速度比find版本大约1:13。这是很有男子气概的,因为它启动的进程更少(这与 find 版本比 ls 版本更快的原因相同)。 tread 还有一些其他的 extglob 解决方案。这完全取决于您是否想要文件或文件链接或诸如此类的问题。同样,代码速度更快,但现在看起来更复杂,那么您的目标是哪种“简单”?
编辑2
请注意,尽管我在评论中说过,与必须排序时的排序开销相比,启动进程的开销可能很小许多/usr/bin/
文件,对我来说,如果我运行包含大约 2500 个文件的代码,它就会开始显示。
答案2
简化脚本的第一种方法是将文件数量保存到变量中,这样您就不必重新计算它。
得出当前目录中文件数量的其他方法:
n=$(ls -l | grep -c ^-)
这里,简化是使用 grep 的-c
选项来计算匹配项。存在错误计数比赛的风险解析 ls 的输出如果有一个名为 的文件$'some\n-file'
,它假装在行首放置一个连字符。
n=$(stat -c %F -- * | grep -c 'regular .*file')
grep 中的.*
是考虑“常规文件”和“常规空文件”匹配。这统计数据命令输出每个文件的类型,*
shell glob 避免了ls
.
如果您熟悉 bash,但听说过zsh 及其强大的文件名通配语法, 你可以:
n=$(zsh -c 'a=( *(.) ); echo ${#a}')
我们在其中创建一个名为的数组,其中填充了仅由扩展名为“纯文件”a
的文件列表过滤的文件。*
.
要正确打印复数,请考虑一个 case 语句:
case $n in
(1) printf "1 file";;
(*) printf "$n files";;
esac
如果您想为不同数量的文件打印不同的消息,则 case 语句将提供更大的灵活性,例如:“没有文件!”。
更简单地,考虑一个条件:
[[ $n == 1 ]] && printf "1 file" || printf "$n files"
最后讨论 Steeldriver 的建议:
n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
printf "%d file%s" "$n" "$(test "$n" -ne 1 && echo s)"
n
这通过打开命令替换来(最终)分配一个值;在临时命令替换中,我创建了两个数组:files
用于所有内容和dirs
仅用于目录。命令替换的最后一个行为是报告两者之间的差异。然后printf
打印文件数量以及适当的复数后缀。
这使用您拥有的任何设置dotglob
;如果你想强制计算(或省略)点文件,那么你应该dotglob
在命令替换中设置或取消设置:
使用 dotglob 的当前值:
n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
强制对点文件进行计数,无论当前的点glob如何:
n=$(shopt -s dotglob;
files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
执行不点文件计数,无论当前的 dotglob 为何:
n=$(shopt -u dotglob;
files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
答案3
find . -maxdepth 1 -type f | awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'
解释:
find . -maxdepth 1 -type f
- 搜索当前目录中的所有文件。awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'
NR
= 从程序接收的行数find
,等于文件数。(NR > 1) ? "s" : "")
- 如果行(文件)数超过 1,则为s
单词添加结尾file
。
答案4
我刚做了这个。我没有ls
使用路径名扩展,因为如果你的文件名有奇怪的字符(例如换行符),它会破坏输出。
#!/usr/bin/env bash
((n=0))
for i in *; do
[[ -f "${i}" ]] && ((n++))
done
((n==1)) && { echo "${n} file"; exit; }
echo "${n} files"