使用 dotglob 的当前值:

使用 dotglob 的当前值:

我正在处理 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" : "")}'

解释:

  1. find . -maxdepth 1 -type f- 搜索当前目录中的所有文件。
  2. 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"

相关内容