Bash循环遍历目录,包括隐藏文件

12

我正在寻找一种在bash中对我的整个目录包括隐藏文件、目录和链接进行简单循环的方法。

最好是使用bash语言实现,但也要考虑通用性。当然,文件名(和目录名)可能包含空格、换行符和符号等特殊字符,但不能包含“/”和ASCII NULL (0×0),即使在第一个字符位置。此外,结果应该排除“.”和“..”目录。

以下是文件生成器,循环需要处理这些文件:

#!/bin/bash
mkdir -p test
cd test
touch A 1 ! "hello world" \$\"sym.dat .hidden " start with space" $'\n start with a newline' 
mkdir -p ". hidden with space" $'My Personal\nDirectory'

因此,我的循环应该像这样(但必须处理上面的棘手问题):

for i in * ;
  echo ">$i<"
done

我最接近成功的尝试是使用ls和bash数组,但它不能正常工作,原因是:

IFS=$(echo -en "\n\b")
l=( $(ls -A .) )
for i in ${l[@]} ; do
echo ">$i<"
done
unset IFS

或者使用bash数组,但“..”目录不被排除:

IFS=$(echo -en "\n\b")
l=( [[:print:]]* .[[:print:]]* )
for i in ${l[@]} ; do
echo ">$i<"
done
unset IFS

1
我认为 While read line; do; echo $line; done <<<$(ls -a) 应该可以工作。 - Ashish
如果你必须修改IFS(如果你遵循Ashish的评论,你不需要这样做),那么我建议你打开一个子shell(括号)以避免任何副作用:(IFS ='stuff' ; do; do) - mcoolive
相反地,我需要避免它们(因为涉及递归调用)。 - Sigmun
@Ashish 这不起作用,唉! - Sigmun
@mcoolive 谢谢你的提示! - Sigmun
显示剩余4条评论
4个回答

28

* 不匹配以 . 开头的文件,所以您需要明确指定:

for i in * .[^.]*; do
    echo ">$i<"
done

.[^.]* 将匹配所有以 . 开头,后跟非.字符,后跟零个或多个字符的文件和目录。换句话说,它类似于更简单的 .*,但排除了 ...。如果您需要匹配像 ..foo 这样的内容,则可以将 ..?* 添加到模式列表中。


循环不是遍历目录结构的正确方式 - 你必须使用递归。 - GodEater
1
Bash 4 可以使用递归模式,如 **/* 遍历目录层次结构。(shopt -s globstar 启用 **,表示匹配路径中的 0 或多个目录)。 - chepner
如果文件数量太大,文件替换功能将无法正常工作。 - mcoolive
bash 内部迭代模式;文件过多只会影响构建参数列表以传递给外部命令(例如,如果 * 产生的文件太多而无法适应为传递给 ls 的参数列表分配的空间,则 ls * 可能会失败)。 - chepner
1
我同意你的方法,@chepner,你的方法比我的答案好得多。为你的回答点赞! - Ashish
@chepner - 太棒了 - 我在bash的文档中没有遇到过这种模式 - 感谢您指出! - GodEater

1
如下所述,此解决方案假定您正在运行GNU bash以及GNU find GNU sort。使用-maxdepth选项可以防止GNU find递归到子目录中。然后使用-print0将每个文件名以0x00字节结尾,而不是通常从-print中获得的换行符。使用sort -z0x00字节之间的文件名进行排序。然后,您可以使用sed去除点和点-点目录条目(尽管GNU find似乎已经排除了..)。我还使用sed去掉了每个文件名前面的./basename也可以这样做,但旧系统没有basename,您可能不信任它正确处理奇怪的字符。

(这些 sed 命令每个都需要两种情况:一种是字符串开头的模式,另一种是在 0x00 字节之间的模式。它们很丑陋,我将它们拆分为单独的函数。)

read 命令没有像某些命令那样的 -z-0 选项,但您可以使用 -d "" 和清空 IFS 环境变量来模拟它。

额外的 -r 选项可以防止反斜杠-换行符组合被解释为行继续符。(一个名为 backslash\\nnewline 的文件会被损坏成 backslashnewline。) 可以看看其他反斜杠组合是否被解释为转义序列。

remove_dot_and_dotdot_dirs()
{
    sed \
      -e 's/^[.]\{1,2\}\x00//' \
      -e 's/\x00[.]\{1,2\}\x00/\x00/g'
}

remove_leading_dotslash()
{
    sed \
      -e 's/^[.]\///' \
      -e 's/\x00[.]\//\x00/g'
}

IFS=""
find . -maxdepth 1 -print0 |
  sort -z |
  remove_dot_and_dotdot_dirs |
  remove_leading_dotslash |
  while read -r -d "" filename
  do
      echo "Doing something with file '${filename}'..."
  done

这是目前最好的答案。我正在寻找更多基于“Bash”的东西(例如使用数组),但它似乎比这个不太可移植。谢谢。 - Sigmun
这不是可移植的:它需要 GNU find-print0 和 GNU sort-z。此外,read -d 也是 bash 扩展名。 - chepner
@chepner:我认为这是可以的,因为问题本身也是不可移植的。转义或处理shell元字符取决于你正在运行的shell。(此外,这个问题被标记为[bash]。)我提到了GNU find,但不是很清楚...我会更明确地说明。 - Kevin J. Chase

0

这可能不是最理想的方法,但我尝试了下面的方式

while read line ; do echo $line; done <<< $(ls -a | grep -v -w ".")

请查看我所做的以下轨迹。 检查输出

0

尝试使用find命令,例如:

find .

这将列出所有递归目录中的文件。

要仅输出不包括前导.或..的文件,请尝试:

find . -type f -printf %P\\n

这包括 ... 路径,但也不涉及文件名中的空格或换行符。 - Sigmun
我会将其修改为 find . -type f,因为OP只对文件而不是目录和其他实体感兴趣... - twalberg
@twalberg 很有趣,但它在每个文件名输出之前添加了额外的"./"。 - Ashish
@Ashish 这可以很容易地通过使用 sed 来修复...例如 find . -type f | sed -e 's;^./;;' - twalberg
是的,谢谢。我一回复就注意到了。 - Ashish
我知道find + sed可能是解决方案,但我发现它不够合适(特别是处理文件名中的空格和换行符)。我猜bash可以为我提供一些简单的循环遍历目录的方法。 - Sigmun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接