【问题标题】:Test -d directory true - subdirectory false (POSIX)测试 -d 目录 true - 子目录 false (POSIX)
【发布时间】:2018-04-25 15:04:03
【问题描述】:

我正在尝试打印给定起始目录中的所有目录/子目录。

for i in $(ls -A -R -p); do 
    if [ -d "$i" ]; then
            printf "%s/%s \n" "$PWD" "$i"
    fi
done; 

此脚本返回在 .目录和该目录中的所有文件,但由于某种原因,子目录的测试失败。所有目录都以 $i 结尾,并且输出看起来完全相同。
假设我有以下结构:

foo/bar/test

echo $i 打印

foo/
bar/
test/

虽然文件夹的内容是这样列出的:

./foo:
file1
file2
./bar:
file1
file2

但是测试语句只是打印:

PWD/TO/THIS/DIRECTORY/foo

由于某种原因,它对第一级目录返回 true,但对所有子目录返回 false。

(ls 可能不是这样做的好方法,我会很高兴找到解决我所有问题的 find 语句,但首先我想知道为什么这个脚本不能像你想象的那样工作。 )

【问题讨论】:

  • 顺便说一句,for i in $(ls ...) 本质上是错误的。请参阅ParsingLsBashPitfalls #1DontReadLinesWithFor
  • 每个目录名后面都有冒号,这样就可以解释为什么-d是假的了。
  • bash -x yourscript 记录每个命令运行(因此,记录[ -d ./foo: ])本身就会使这一点更加明显。
  • 另外,使用printf 的正确方法是将数据和格式字符串分开。即printf '%s/%s\n' "$PWD" "$i"。这在 bash 的 printf 中并不像在 C 中那么重要(恶意格式字符串会导致实际的安全漏洞),但是在存在异常或令人惊讶的文件名时仍然可能发生错误。
  • @PeterFlash,您是否使用sh -x yourscriptbash -x yourscript 运行脚本来记录它?完成后,您将知道失败的确切 [ 命令。

标签: shell unix testing directory posix


【解决方案1】:

正如 cmets 中所指出的,问题在于目录名称包含 :,因此 -d 为假。

我猜这个命令会给你你想要的输出(虽然它需要 Bash):

# enable globstar for ** 
# disabled in non-interactive shell (e.g. a script)
shopt -s globstar 

# print each path ending in a / (all directories)
# ** expands recursively
printf '%s\n' **/*/

标准方法是自己进行递归,或者使用find

find . -type d

【讨论】:

  • OP 要求提供符合 POSIX 标准的答案,因此此处可能不适合使用 bashisms。
【解决方案2】:

考虑你的输出:

dir1:
dir1a

现在,以下将是正确的:

[ -d dir1/dir1a ]

但这不是您的代码所做的;相反,它运行:

[ -d dir1a ]

为避免这种情况,不要尝试解析ls;如果你想在基线 POSIX sh 中实现递归,请自己动手:

callForEachEntry() {
  # because calling this without any command provided would try to execute all found files
  # as commands, checking for safe/correct invocation is essential.
  if [ "$#" -lt 2 ]; then
    echo "Usage: callForEachEntry starting-directory command-name [arg1 arg2...]" >&2
    echo "  ...calls command-name once for each file recursively found" >&2
    return 1
  fi
  # try to declare variables local, swallow/hide error messages if this fails; code is
  # defensively written to avoid breaking if recursing changes either, but may be faulty if
  # the command passed as an argument modifies "dir" or "entry" variables.
  local dir entry 2>/dev/null ||: "not strict POSIX, but available in dash"
  dir=$1; shift
  for entry in "$dir"/*; do
    # skip if the glob matched nothing
    [ -e "$entry" ] || [ -L "$entry" ] || continue
    # invoke user-provided callback for the entry we found
    "$@" "$entry"
    # recurse last for if on a baseline platform where the "local" above failed.
    if [ -d "$entry" ]; then
      callForEachEntry "$entry" "$@"
    fi
  done
}

# call printf '%s\n' for each file we recursively find; replace this with the code you
# actually want to call, wrapped in a function if appropriate.
callForEachEntry "$PWD" printf '%s\n'

find 也可以安全使用,但 不能 替代原始代码中使用 ls 的方式——for dir in $(find . -type d) 也有问题。相反,请参阅Using Find 的“复杂操作”和“批量操作”部分。

【讨论】:

  • 正确。我该如何解决这个问题?我应该使用不同的方法来读取子目录/文件吗?
  • @PeterFlash,是的——请参阅编辑添加建议的解决方案。
  • @PeterFlash 只使用find?
  • @marbu, ...所以,find 的问题是需要-print0 扩展才能使其安全。或者-exec,当然是既标准定义又安全的;但是默认的打印操作会生成一个以换行符分隔的输出列表,因此在存在不受控制的文件名(可能包含换行符文字)时它是不安全的。
  • @CharlesDuffy 同意,在进一步处理输出时使用-print0 是个好主意
猜你喜欢
  • 2013-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多