【问题标题】:Recursively list hidden files without ls, find or extendedglob递归列出不带 ls、find 或 extendedglob 的隐藏文件
【发布时间】:2016-01-18 20:23:34
【问题描述】:

作为一个练习,我为自己设定了使用 bash 内置函数递归列出文件的任务。我特别不想使用 ls 或 find,我更不想使用 setopt extendedglob。以下似乎有效,但我看不到如何使用 /.* 扩展它以列出隐藏文件。有没有简单的解决方法?

g() { for k in "$1"/*; do # loop through directory
[[ -f "$k" ]] && { echo "$k"; continue; }; # echo file path
[[ -d "$k" ]] && { [[ -L "$k" ]] && { echo "$k"; continue; }; # echo symlinks but don't follow
g "$k"; }; # start over with new directory
done; }; g "/Users/neville/Desktop" # original directory

稍后添加:抱歉 - 我应该说:'bash-3.2 on OS X'

【问题讨论】:

  • bash 4.1 开始,您可以在==!= 的参数中使用扩展模式,而无需明确打开extglob,如果您反对的话。

标签: bash recursion hidden-files built-in


【解决方案1】:

改变

for k in "$1"/*; do

for k in "$1"/* "$1"/.[^.]* "$1"/..?*; do

第二个 glob 匹配名称以点开头的所有文件,后跟除点以外的任何内容,而第三个匹配名称以两个点开头的所有文件,后跟一些东西。在它们两者之间,它们将匹配除了条目...之外的所有隐藏文件。

不幸的是,除非设置了 shell 选项 nullglob,否则如果没有名称匹配的文件(极有可能在第三个的情况下),那些(如第一个 glob)可能会保持原样,因此有必要验证名称实际上是一个文件。

另一种方法是使用更简单的 glob "$1"/.*,它将始终与 ... 目录条目匹配,因此将始终被替换。在这种情况下,有必要从列表中删除这两个条目:

for k in "$1"/* "$1"/.*; do
  if ! [[ $k =~ /\.\.?$ ]]; then
    # ...
  fi
done

(尽管如此,"$1"/* 仍有可能保留在列表中。所以这并没有起到太大的帮助。)

【讨论】:

  • 我实际上比我更喜欢这个答案(这就是我个人的做法),因为它可以在 Bash 以外的 shell 中工作。但由于 OP 确实特别提到了“bash”,我继续贡献了 GLOBIGNORE 解决方案,它会自动跳过 ...。 :)
  • 这两个都很好,我可以看到他们在做什么,但我不明白为什么。没有它们,使用“$1”/.* 会慢慢产生许多目录:/Users/neville/Desktop/test dir/././././././././././。 - 为什么是这样?点文件是否像目录的符号链接一样,如果是,为什么我的符号链接陷阱没有停止它?如果我很乐意列出所有 UNIX 点和双点文件,应该如何修改?
【解决方案2】:

将 GLOBIGNORE 文件设置为排除 ...,这会隐式打开“shopt -u dotglob”。然后您的原始代码无需其他更改即可工作。

user@host [/home/user/dir]
$ touch file
user@host [/home/user/dir]
$ touch .dotfile
user@host [/home/user/dir]
$ echo *
file
user@host [/home/user/dir]
$ GLOBIGNORE=".:.."
user@host [/home/user/dir]
$ echo *
.dotfile file

请注意,这是特定于 bash 的。特别是,它在 ksh 中不起作用。

【讨论】:

    【解决方案3】:

    您可以为for指定多个参数:

    for k in "$1"/* "$1"/.*; do
    

    但是,如果您确实在目录中搜索 .*,您应该知道它还会为您提供 ... 文件。如果"$1"/* glob 匹配,您也可能会得到一个不存在的文件,所以我也会检查一下。

    考虑到这一点,这就是我纠正循环的方式:

    g() {
        local k subdir
        for k in "$1"/* "$1"/.*; do # loop through directory
            [[ -e "$k" ]] || continue  # Skip missing files (unmatched globs)
            subdir=${k##*/}
            [[ "$subdir" = . ]] || [[ "$subdir" = .. ]] && continue  # Skip the pseudo-directories "." and ".."
    
            if [[ -f "$k" ]] || [[ -L "$k" ]]; then
                printf %s\\n "$k"  # Echo the paths of files and symlinks
            elif [[ -d "$k" ]]; then
                g "$k"  # start over with new directory
            fi
        done
    }
    g ~neville/Desktop
    

    这里看起来很时髦的 ${k##*/} 只是获取文件基本名称的一种快速方法,而 local 被放入以便变量不会修改 shell 中的任何现有变量。

    我更改的另一件事是将echo "$k" 更改为printf %s\\n "$k",因为echo 在其参数处理方面存在不可弥补的缺陷,应避免使用以回显未知变量。 (请参阅Rich's sh tricks 以了解如何解释;归结为 -n 和 -e 在工作中扔扳手。)

    顺便说一句,这不会打印套接字或 fifos - 这是故意的吗?

    【讨论】:

      猜你喜欢
      • 2021-12-26
      • 1970-01-01
      • 1970-01-01
      • 2014-12-26
      • 1970-01-01
      • 2012-10-24
      • 1970-01-01
      • 2019-09-06
      • 1970-01-01
      相关资源
      最近更新 更多