使用作为bash脚本参数传递的glob表达式

时间:2015-12-30 00:37:05

标签: linux bash shell

TL; DR:

./myscript foo* myscriptvar=$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循环中的通配符扩展有关。

1 个答案:

答案 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表达式的文件。

其他一些注意事项:

  • 总是在扩展中加上双引号!如果不这样做,将导致应用字符串拆分和全局扩展(按此顺序)的附加步骤。如果想要 globbing,就像# 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 "$@"; dofor dir in $*; do!后面的这些调用将列表的每个元素与for dir in $@; do的第一个字符组合在一起(默认情况下,它包含空格,选项卡和新行的顺序),然​​后将结果字符串拆分为任何IFS在其中找到的字符,然后将结果列表的每个组件扩展为glob。
  • IFS作为参数传递给/dev/null是一种安全措施:它确保您在单参数和多参数情况之间没有不同的行为(例如,{{ 1}}默认只在传递多个参数时在输出中打印文件名),并确保如果没有传递任何其他文件名(grep,则不能让grep挂起尝试从stdin读取不会在这里做,但grep可以)。
  • 对自己的变量使用小写名称(而不是系统和shell提供的变量,它们具有全大写的名称)符合POSIX指定的约定;请参阅the POSIX specification regarding environment variables的第四段,记住环境变量和shell变量共享一个命名空间。