如何在Bash中获取目录列表并将它们扩展为命令行参数?

10
我正在编写一个bash脚本,需要在一步中获取目标目录(可能还包含文件)中的目录列表(变量),然后将它们作为参数展开到一个Python脚本中。 示例:
/stuff/a dir/
/stuff/b other/
/stuff/c/

我需要在Bash脚本中调用:

script.py "a dir/" "b other/" "c/"
或者,更替代地说,转义空格:
script.py a\ dir/ b\ other/ c/

我需要确保脚本仅针对目录“stuff”被调用一次。

有没有简单的方法可以实现这种要求?我已经在Google上查找了一些信息,最好的解决方案是需要我知道有多少个目录。

4个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
18
这是一个查找的任务。
find /stuff -type d -exec script.py {} +
当你使用-exec时,花括号{}会被替换为匹配文件的名称,而+表示命令的结束(如果您想告诉find执行其他操作)。这是使用find执行命令的理想方式,因为它可以正确处理具有不寻常字符(例如空格)的文件名。 find相当灵活,特别是如果您拥有通常捆绑在Linux发行版中的GNU版本。
# Don't recurse into subdirectories.
find /stuff -maxdepth 1 -type d -exec script.py {} +

# Pass in a/, b/, c/ instead of /stuff/a/, /stuff/b/, /stuff/c/.
find /stuff -type d -printf '%P\0' | xargs -0 script.py
在第二个例子中,注意仔细使用\0xargs -0来使用NUL字符作为文件名的分隔符。虽然可能看起来很奇怪,但这样可以使命令即使在您的目录名称中使用新行\n之类的非常规字符时也能正常工作。
或者,您可以仅使用shell内置命令来完成此操作。虽然我不建议这样做,但出于教育目的,以下是如何执行此操作:
# Start with an empty array.
DIRS=()

# For each file in /stuff/...
for FILE in /stuff/*; do
    # If the file is a directory add it to the array. ("&&" is shorthand for
    # if/then.)
    [[ -d $FILE ]] && DIRS+=("$FILE")

    # (Normally variable expansions should have double quotes to preserve
    # whitespace; thanks to bash magic we don't them inside double brackets.
    # [[ ]] has special parsing rules.)
done

# Pass directories to script. The `"${array[@]}"` syntax is an unfortunately
# verbose way of expanding an array into separate strings. The double quotes
# and the `[@]` ensure that whitespace is preserved correctly.
script.py "${DIRS[@]}"

1
请注意,find命令将递归进入目录。您需要使用“-maxdepth 1”选项。另外,“-exec script.py {} +”选项可能会多次调用脚本,如果有很多目录存在的话。从问题描述中无法确定是否可以接受这种情况。 - Paul V
有点偏题,但是为什么你不建议只使用shell内置命令来完成它呢? - Charles Randall
@Charles Randall:关于“内置”与“非内置”.. find命令具有一些特别有用的功能,用于处理文件;比“内置”命令更加实用,例如在处理空格嵌入的文件名时(请参见man find中的-print0),以及执行exec命令.. 有关内置命令的如何/什么/为什么的更多信息,请参见:http://unix.stackexchange.com/questions/11454/what-is-the-difference-between-a-builtin-command-and-one-that-is-not - Peter.O
@Charles 我并不是想暗示那个仅限于bash的脚本有问题。它是完全没有缺陷的好代码,至少我所知道的没有。只是相比使用find命令,它更冗长而已。 - John Kugelman

6
一个更简单的解决方案,不会像find一样创建新进程,可以这样实现:
for f in stuff/*; do
  if [ -d "$f" ]; then
     ./script.py "$f"
  fi
done

2
你可以使用 find 命令,并告诉它只打印出目录,使用 -type d 参数。你的命令将如下所示:
script.py $(find /stuff/* -type d)
如果您担心空格和其他特殊字符,可以这样操作:
script.py $(find /stuff/* -type d | while read line; do echo "\"$line"\"; done)

有没有一种方法可以将单独的输出用引号括起来,以便正确处理目录名称中的空格? - Charles Randall

1
find /stuff/* -type d -maxdepth 1 -print0 | xargs -0 script.py

这将查找 /stuff 下的所有目录,但不递归,并将它们传递给 script.py,并确保即使目录名称中有空格也能正确传递。


这个几乎可以工作,但它也在输出中列出了 /stuff,这会给我带来问题。 - Charles Randall
根据您的评论进行了更新。 - Steve Prentice
这将递归到子目录中。使用“-mindepth 1”和“maxdepth 1”而不使用*仅打印直接目录。 - Sam Daniel

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