当./myscript foo*
myscript
与var=$1
硬编码调用./myscript
相同时,为什么不调用var=foo*
?
我在写作的bash脚本中遇到了一个奇怪的问题。我确信有一个简单的解释,但我无法弄清楚。
我正在尝试传递一个命令行参数,以便在脚本中指定为变量。
我希望脚本允许2个命令行参数,如下所示:
$ bash my_bash_script.bash args1 args2
在我的脚本中,我分配了这样的变量:
ARGS1=$1
ARGS2=$2
Args 1是要添加到输出文件的字符串描述符。
Args 2是一组目录:" dir1,dir2,dir3",我将其传递为dir*
当我在脚本中将dir*
分配给ARGS2时,它可以正常工作,但是当我将dir*
作为第二个命令行参数传递时,它只在{8}的通配符扩展中包含dir1
{1}}。
我认为这与shell如何处理通配符有关(即使以args形式传递),但我并不理解它。
任何帮助都将不胜感激。
我有一组目录:
dir*
在这些目录中,我尝试通过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"
。
我想将*report.txt
作为第二个参数传递给脚本并将其存储在变量ARGS2中,然后使用它在dir_*_map
和".status"
的每个目录中进行搜索文件。
问题是从命令行传递".report"
并不能提供目录列表,而只是列表中的第一项。如果我在脚本中分配变量dir_*_map
,它就像我想要的那样工作。
事实证明,在引号中传递第二个参数允许通配符扩展适用于ARGS2=dir_*_map
"dir_*_map"
以下是脚本的示例调用:
#!/usr/bin/env bash
ARGS1=$1
ARGS2=$2
touch $ARGS1".extension"
for i in /$ARGS2/*.status
do
grep -e "string" $i >> $ARGS1".extension"
done
我不完全理解何时/为什么必须在引号中传递一些参数,但我认为它与for循环中的通配符扩展有关。
答案 0 :(得分:4)
分配(例如var=foo*
)不会扩展整数 - 也就是说,当您运行var=foo*
时,文字字符串foo*
会被放入变量foo
,而不是与foo*
匹配的文件列表。
相比之下,在命令行上不加引号使用foo*
会扩展glob,将其替换为单个名称列表,每个名称都作为单独的参数传递。
因此,运行./yourscript foo*
不会将foo*
作为$1
传递,除非不存在与该glob表达式匹配的文件;相反,它变成类似./yourscript foo01 foo02 foo03
的东西,每个参数都在命令行的不同位置。
运行./yourscript "foo*"
函数作为变通方法的原因是脚本内部没有引用扩展,允许在稍后的时间扩展glob。但是,这是不好的做法:全局扩展与字符串拆分同时发生(意味着依赖此行为会移除您传递包含IFS
中找到的字符的文件名的能力,通常是空格),也意味着您不能传递文字文件名时,它们也可以被解释为globs(如果你有一个名为[1]
的文件和一个名为1
的文件,传递[1]
将始终被1
替换)
构建这个的惯用方法是shift
离开第一个参数,然后迭代后续的参数,如下所示:
#!/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
而不是调用{{}},可以提高所有这些效率。 {1}}单独基于每个文件:
grep
上述两个脚本都希望在调用shell上引用通过而不是的整数。因此,使用形式如下:
#!/bin/bash
out_base=$1; shift
find "$@" -maxdepth 1 -type f -name '*.status' \
-exec grep -h -- /dev/null '{}' + \
>"${out_base}.extension"
这比将globs传递给您的脚本要好得多(然后需要将它们扩展为检索要使用的实际文件);它适用于包含空格的文件名(其他做法没有),以及名称本身为glob表达式的文件。
其他一些注意事项:
# being unquoted, this expands the glob into a series of separate arguments
your_script descriptor dir_*_map
的情况一样,那么在glob表达式开始之前结束引号。"$dir"/*.status
正好等同于for dir; do
,它遍历参数。不要错误地使用for dir in "$@"; do
或for dir in $*; do
!后面的这些调用将列表的每个元素与for dir in $@; do
的第一个字符组合在一起(默认情况下,它包含空格,选项卡和新行的顺序),然后将结果字符串拆分为任何IFS
在其中找到的字符,然后将结果列表的每个组件扩展为glob。IFS
作为参数传递给/dev/null
是一种安全措施:它确保您在单参数和多参数情况之间没有不同的行为(例如,{{ 1}}默认只在传递多个参数时在输出中打印文件名),并确保如果没有传递任何其他文件名(grep
,则不能让grep
挂起尝试从stdin读取不会在这里做,但grep
可以)。