Linux 使用作为bash脚本参数传递的glob表达式 TL;博士:
当Linux 使用作为bash脚本参数传递的glob表达式 TL;博士:,linux,bash,shell,Linux,Bash,Shell,当myscript具有var=$1时,为什么调用/myscript foo*与使用var=foo*硬编码调用/myscript不一样 长形 我在写bash脚本时遇到了一个奇怪的问题。我相信有一个简单的解释,但我想不出来 我试图传递一个命令行参数,作为脚本中的变量指定 我希望脚本允许2个命令行参数,如下所示: $ bash my_bash_script.bash args1 args2 在我的脚本中,我分配了如下变量: ARGS1=$1 ARGS2=$2 Args 1是要添加到输出文件的字符
myscript
具有var=$1
时,为什么调用/myscript foo*
与使用var=foo*
硬编码调用/myscript
不一样
长形 我在写bash脚本时遇到了一个奇怪的问题。我相信有一个简单的解释,但我想不出来 我试图传递一个命令行参数,作为脚本中的变量指定 我希望脚本允许2个命令行参数,如下所示:
$ bash my_bash_script.bash args1 args2
在我的脚本中,我分配了如下变量:
ARGS1=$1
ARGS2=$2
Args 1是要添加到输出文件的字符串描述符
args2是一组目录:“dir1,dir2,dir3”,我将其传递为dir*
当我在脚本中将dir*
分配给ARGS2时,它可以正常工作,但当我将dir*
作为第二个命令行参数传递时,它只在dir*
的通配符扩展中包含dir1
我假设这与shell如何处理通配符有关(即使作为args传递),但我并不真正理解它
任何帮助都将不胜感激
环境/用途 我有一组目录:
dir_1_y_map, dir_1_x_map, dir_2_y_map, dir_2_x_map,
... dir_10_y_map, dir_10_x_map...
在这些目录中,我试图通过*.status>访问扩展名为>“.status”
的文件,并通过*report.txt
访问.txt”
的文件
我想将dir\u*\ u map
作为第二个参数传递给脚本,并将其存储在变量ARGS2中,然后使用它在每个目录中搜索”。status“
和”。report“
文件
问题在于,从命令行传递dir\u*\ u map
不会给出目录列表,而只是列表中的第一项。如果我在脚本中指定变量ARGS2=dir\u*\ u map
,它将按照我的意愿工作
解决方法:引用
事实证明,在引号中传递第二个参数可以使通配符扩展适用于“dir\u*\ u map”
以下是脚本的调用示例:
sh ~/path/to/script descriptor "dir_*_map"
我不完全理解何时/为什么某些参数必须以引号传递,但我认为这与for循环中的通配符扩展有关。解决“为什么”
与var=foo*
中的赋值一样,不展开globs——也就是说,当运行var=foo*
时,文本字符串foo*
被放入变量foo
,而不是匹配foo*
的文件列表
相比之下,在命令行中不加引号地使用foo*
会扩展glob,将其替换为单个名称的列表,每个名称都作为单独的参数传递
因此,运行/yourscript foo*
不会将foo*
传递为$1
,除非不存在与该glob表达式匹配的文件;相反,它变成了类似于/yourscript foo01 foo02 foo03
,每个参数位于命令行的不同位置
运行/yourscript“foo*”
作为一种解决办法的原因是脚本内部的无引号扩展允许glob在稍后的时间进行扩展。但是,这是一种不好的做法:glob扩展与字符串拆分同时发生(这意味着依赖此行为将无法传递包含在IFS
中的字符的文件名,通常为空格),还意味着无法传递文本文件名,因为它们也可以被解释为glob(如果您有一个名为[1]
的文件和一个名为1
的文件,则传递的[1]
将始终替换为1
)
惯用法
构建此参数的惯用方法是将第一个参数移开,然后迭代后续参数,如下所示:
#!/bin/bash
out_base=$1; shift
shopt -s nullglob # avoid generating an error if a directory has no .status
for dir; do # iterate over directories passed in $2, $3, etc
for file in "$dir"/*.status; do # iterate over files ending in .status within those
grep -e "string" "$file" # match a single file
done
done >"${out_base}.extension"
如果在一个目录中有多个.status
文件,则可以通过使用find
调用grep
并使用尽可能多的参数,而不是逐个文件调用grep
来提高效率:
#!/bin/bash
out_base=$1; shift
find "$@" -maxdepth 1 -type f -name '*.status' \
-exec grep -h -- /dev/null '{}' + \
>"${out_base}.extension"
上述两个脚本都希望在调用shell上引用传递的glob而不是。因此,用法如下:
# being unquoted, this expands the glob into a series of separate arguments
your_script descriptor dir_*_map
这比将glob传递给脚本要好得多(然后需要将其展开以检索要使用的实际文件);它可以正确处理包含空格的文件名(另一种做法没有),以及名称本身就是glob表达式的文件
其他一些值得注意的问题:
- 始终在扩展名周围加上双引号!如果不这样做,将导致应用字符串拆分和全局扩展(按该顺序)的附加步骤。如果要进行全局操作,如“
”$dir”/*.status
,请在全局表达式开始之前结束引号
for dir;do
与“$@”do
中的for dir完全相同,后者迭代参数。不要错误地将$*;do
中的for dir或
for dir in$@;do用于列表中的每个元素!后一种调用将列表中的每个元素与IFS
的第一个字符组合在一起(默认情况下,按该顺序包含空格、制表符和换行符),然后在其中找到的任何IFS
字符上拆分结果字符串,然后将结果列表的每个组件展开为全局
/dev/null
作为参数传递给grep
是一种安全措施:它确保单参数和多参数情况下没有不同的行为(例如,grep
默认为仅在传递多个参数时才在输出中打印文件名),并确保在stdin没有传递任何附加文件名的情况下,grep
不会挂起尝试从stdin读取(这在这里是find
不起作用的,但是
# being unquoted, this expands the glob into a series of separate arguments
your_script descriptor dir_*_map