我将从您提到两次的 Windows 批处理文件开始。最大的区别在于,在 Windows 上,shell 不会进行任何通配,而是将其留给各种命令(并且每个命令的执行方式不同),而在 Linux/Unix 上通配通常由 shell 完成,并且可以通过引用或转义来防止。 Windows 方法和 Linux 方法都有其优点,并且它们在不同的用例中进行了不同的比较。
对于普通的 bash 用户,引用
./do-for.sh repo-'*' git commit -a -m "Added new files."
或转义
./do-for.sh repo-\* git commit -a -m "Added new files."
是最简单的解决方案,因为它们是他们每天都在使用的。如果您的用户需要不同的语法,那么到目前为止,您已经提出了所有解决方案,我将在提出自己的解决方案之前将其分为四类(请注意,在下面的每个示例中,do-for.sh 代表 不同 脚本采用相应的解决方案,可以在其他答案之一中找到。)
这很有效,类似于其他 shell 命令在类似情况下采用的解决方案,并且仅当您的目录名称扩展包含与分隔符完全相同的目录名称时才会失败(不太可能发生的事件,在上面的例子,但一般情况下可能会发生。)
这行得通,但同样,它涉及引用,甚至可能是嵌套的,与更常见的通配符引用相比,它没有任何意义。
并考虑处理目录,直到您找到一个不是目录的名称。这在许多情况下都有效,但可能会在一些晦涩难懂的情况下失败(例如,当您有一个以命令命名的目录时)。
我的解决方案不属于上述任何类别。事实上,我建议不要在脚本的第一个参数中使用 * 作为通配符。 (这类似于split 命令使用的语法,您为要生成的文件提供非全局前缀参数。)我有两个版本(下面的代码)。对于第一个版本,您将执行以下操作:
# repo- is a prefix: the command will be excuted in all
# subdirectories whose name starts with it
./do-for.sh repo- git commit -a -m "Added new files."
# The command will be excuted in all subdirectories
# of the current one
./do-for.sh . git commit -a -m "Added new files."
# If you want the command to be executed in exactly
# one subdirectory with no globbing at all,
# '/' can be used as a 'stop character'. But why
# use do-for.sh in this case?
./do-for.sh repo/ git commit -a -m "Added new files."
# Use '.' to disable the stop character.
# The command will be excuted in all subdirectories of the
# given one (paths have to be always relative, though)
./do-for.sh repos/. git commit -a -m "Added new files."
第二个版本涉及使用 shell 不知道的通配符,例如 SQL 的 % 字符
# the command will be excuted in all subdirectories
# matching the SQL glob
./do-for.sh repo-% git commit -a -m "Added new files."
./do-for.sh user-%-repo git commit -a -m "Added new files."
./do-for.sh % git commit -a -m "Added new files."
第二个版本更灵活,因为它允许非最终的 glob,但对于 bash 世界的标准较低。
代码如下:
#!/bin/bash
if [ "$#" -lt 2 ]; then
echo "Usage: ${0##*/} PREFIX command..." >&2
exit 1
fi
pathPrefix="$1"
shift
### For second version, comment out the following five lines
case "$pathPrefix" in
(*/) pathPrefix="${pathPrefix%/}" ;; # Stop character, remove it
(*.) pathPrefix="${pathPrefix%.}*" ;; # Replace final dot with glob
(*) pathPrefix+=\* ;; # Add a final glob
esac
### For second version, uncomment the following line
# pathPrefix="${pathPrefix//%/*}" # Add a final glob
tmp=${pathPrefix//[^\/]} # Count how many levels down we have to go
maxDepth=$((1+${#tmp}))
# Please note that this won’t work if matched directory names
# contain newline characters (comment added for those bash freaks who
# care about extreme cases)
declare -a directories=()
while read d; do
directories+=("$d")
done < <(find . -maxdepth "$maxDepth" -path ./"$pathPrefix" -type d -print)
curDir="$(pwd)"
for d in "${directories[@]}"; do
cd "$d";
"$@"
cd "$curDir"
done
与在 Windows 中一样,如果前缀包含空格,您仍然需要使用引号
./do-for.sh 'repository for project' git commit -a -m "Added new files."
(但是如果前缀不包含空格,你可以避免引用它,它会正确处理任何以该前缀开头的包含空格的目录名;有明显的变化,第二个中的 %-patterns 也是如此版本。)
请注意 Windows 和 Linux 环境之间的其他相关差异,例如路径名区分大小写、字符被视为特殊字符的差异等等。