【问题标题】:Using a glob expression passed as a bash script argument使用作为 bash 脚本参数传递的 glob 表达式
【发布时间】:2015-12-31 03:19:49
【问题描述】:

TL;DR:

为什么当myscript 具有var=$1 时不调用./myscript foo* 与使用硬编码的var=foo* 调用./myscript 相同?


更长的形式

我在编写的 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

我认为这与 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".report.txt" 通过*report.txt 访问扩展名为".status" 的文件。

我想将dir_*_map 作为第二个参数传递给脚本并将其存储在变量ARGS2 中,然后使用它在每个目录中搜索".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

这是一个脚本调用示例:

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

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

【问题讨论】:

  • 将脚本中的第一行用作#!/bin/bash,而不是调用bash myscript arg1 ...。可能无法解决您的问题,但不是好的做法。祝你好运。
  • 你真的需要给我们最小的示例程序(集)来说明你的问题。您可能只需要使用"$ARG1"$ARG2 来解决您的问题,但是如果没有真正的证据证明发生了什么,我们只能推测,嗯? ;-) 请编辑你的 Q,而不是在 cmets 中创建一条消息链。祝你好运。
  • 谢谢,我在我的代码示例中进行了编辑。
  • 好节目,很高兴您更新了您的 Q。现在我们可以提供帮助。但是当ARGS2=dir1, dir2, dir3 可能与for i in /{$ARGS2}/*.status 一起使用时,您的for i in /$ARGS2/*.status ;做 并且您需要删除dir1,dir2,dir3 之类的空格。会和这个一起吃的,你也试试。祝你好运。
  • 学习通过http://shellcheck.net 运行代码,您会发现for 行缺少;do 并且for i 中的i 从未在您的块中使用的代码。你想在哪里使用 grep $arg1 $i/.... ?我猜?

标签: linux bash shell


【解决方案1】:

解决“为什么”

Assignments,如var=foo*,不要扩展glob——也就是说,当你运行var=foo*时,文字字符串foo*被放入变量foo,而不是匹配@的文件列表987654326@.

相比之下,在命令行中不带引号使用 foo* 会扩展全局,将其替换为单个名称的列表,每个名称都作为单独的参数传递

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

运行./yourscript "foo*" 函数作为解决方法的原因是脚本内未引用的扩展允许在以后扩展全局。但是,这是不好的做法:全局扩展与字符串拆分同时发生(这意味着依赖此行为会消除您传递包含在 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"

上面的两个脚本都希望在调用 shell 上引用传递 not 的 glob。因此,用法是这样的:

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

这比将 glob 传递给脚本要好得多(然后需要扩展它们以检索要使用的实际文件);它适用于包含空格的文件名(其他做法没有),以及名称本身就是 glob 表达式的文件。


其他一些注意事项:

  • 始终在展开式两边加上双引号!不这样做会导致应用字符串拆分和全局扩展(按此顺序)的额外步骤。如果您想要进行通配符,例如"$dir"/*.status,则在 glob 表达式开始之前结束引号。
  • for dir; do 完全等同于 for dir in "$@"; do,它迭代参数。不要误用for dir in $*; dofor dir in $@; do!后面的这些调用将列表的每个元素与IFS 的第一个字符组合在一起(默认情况下,按顺序包含空格、制表符和换行符),然后将结果字符串拆分为在其中找到的任何IFS 字符,然后将结果列表的每个组件展开为一个 glob。
  • /dev/null 作为参数传递给grep 是一种安全措施:它确保您在单参数和多参数情况之间没有不同的行为(例如,grep 默认为打印仅在传递多个参数时才在输出中包含文件名),并确保您不能让 grep 挂起尝试从标准输入读取,如果它根本没有传递额外的文件名(find 不会在这里做,但 xargs可以)。
  • 为您自己的变量使用小写名称(与系统和 shell 提供的变量具有全大写名称相反)符合 POSIX 指定的约定;请参阅the POSIX specification regarding environment variables 的第四段,请记住环境变量和 shell 变量共享一个命名空间。

【讨论】:

  • 哇,太棒了,谢谢查尔斯!我不是一个受过经典训练的程序员/计算机科学家,所以我经常在不知不觉中以低效或完全不正确的方式处理问题。例如,我最近在某处读到建议对变量使用全大写名称(以便于识别),但我没有意识到这仅限于系统和 shell 提供的变量。无论如何,非常感谢您的详细回复,我从这篇文章中学到的东西比我希望的要多得多。新年快乐。
  • 另外,我不知道使用shift 来迭代参数,这对我很有帮助。谢谢!
  • shell 中有很多货物崇拜编程——人们从(通常是糟糕的)示例而不是规范资源中学习,然后用这种方式教授他们学到的东西。不幸的是,至少可以这么说。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-12
  • 1970-01-01
  • 1970-01-01
  • 2015-05-13
  • 1970-01-01
  • 2017-05-15
  • 1970-01-01
相关资源
最近更新 更多