繁体   English   中英

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

[英]Using a glob expression passed as a bash script argument

TL; DR:

为什么当myscript var=$1与使用var=foo*硬编码的./myscript调用时,为什么不调用./myscript 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*作为第二个命令行参数传递时,它仅在dir*的通配符扩展中包括dir1

我认为这与外壳处理通配符(即使以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访问扩展名为".report.txt"

我想将dir_*_map作为第二个参数传递给脚本,并将其存储在变量ARGS2中,然后使用它在每个目录中搜索".status"".report"文件。

问题是从命令行传递dir_*_map不会给出目录列表,而只是给出列表中的第一项。 如果我在脚本中分配变量ARGS2=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

这是脚本的示例调用:

sh ~/path/to/script descriptor "dir_*_map"

我不完全理解何时/为什么必须在引号中传递某些参数,但我认为这与for循环中的通配符扩展有关。

解决“为什么”

就像var=foo*那样,赋值不会扩展全局变量-也就是说,当您运行var=foo* ,文字字符串foo*被放入变量foo ,而不是与foo*匹配的文件列表中。

相比之下,在命令行上不加引号的foo*扩展了glob,将其替换为单个名称列表,每个名称均作为单独的参数传递

因此,运行./yourscript foo*不会将foo*作为$1传递,除非不存在与该glob表达式匹配的文件。 相反,它变成类似于./yourscript foo01 foo02 foo03东西,每个参数都位于命令行的不同位置。

运行./yourscript "foo*"作为替代方法的原因是脚本内部未引用的扩展允许在以后扩展glob。 但是,这是一种不好的做法:全局扩展与字符串拆分同时发生(这意味着依靠此行为将使您无法传递包含在IFS找到的字符(通常为空格)的文件名),并且还意味着在以下情况下不能传递文字文件名:它们也可以解释为glob(如果您有一个名为[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 ,而不是逐个文件地单独调用grep ,从而使所有这一切更有效。

#!/bin/bash
out_base=$1; shift

find "$@" -maxdepth 1 -type f -name '*.status' \
  -exec grep -h -- /dev/null '{}' + \
  >"${out_base}.extension"

上面的两个脚本都希望传递的glob在调用shell上被引用。 因此,用法的形式为:

# being unquoted, this expands the glob into a series of separate arguments
your_script descriptor dir_*_map

这比将glob传递到脚本(然后将其扩展以检索要使用的实际文件)要好得多。 它可以正确处理包含空格的文件名(其他做法则不这样做)以及名称本身就是glob表达式的文件。


其他注意事项:

  • 始终在扩展名两边加上双引号! 否则,将导致附加的字符串拆分和全局扩展(按此顺序)步骤。 如果遍历(例如"$dir"/*.status 。status的情况),请在遍历表达式开始之前结束引号。
  • for dir; do for dir; do等同for dir in "$@"; do for dir in "$@"; do ,它遍历参数。 不要犯for dir in $*; do中使用for dir in $*; do的错误for dir in $*; do for dir in $*; dofor dir in $@; do for dir in $@; do吧! 后面的这些调用将列表的每个元素与IFS的第一个字符(默认情况下,该字符按顺序包含空格,制表符和换行符)组合在一起,然后在其中找到的所有IFS字符上分割结果字符串,然后展开每个字符结果列表的组成部分作为一个整体。
  • /dev/null作为参数传递给grep是一种安全措施:确保单参数和多参数情况之间没有不同的行为(例如, grep默认仅在传递时在输出中打印文件名)多个参数),并确保您不会让grep挂起尝试从stdin读取(如果它根本没有传递任何其他文件名)(在这里find ,但xargs可以)。
  • 为自己的变量使用小写名称(与系统和外壳程序提供的变量全为大写)不同,这符合POSIX指定的约定; 有关环境变量 ,请参见POSIX规范的第四段,请记住,环境变量和shell变量共享一个名称空间。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM