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