【问题标题】:Looping over shell script arguments and passing quoted arguments to function循环 shell 脚本参数并将带引号的参数传递给函数
【发布时间】:2018-05-31 16:46:31
【问题描述】:

我在下面有一个脚本,它获取 bash 脚本目录,然后解析命令的标志以从源文件中运行特定函数。

scripts 目录中给定这个函数:

function reggiEcho () {
  echo $1
}

这里是一些电流输出的例子

$ reggi --echo hello
hello
$ reggi --echo hello world
hello
$ reggi --echo "hello world"
hello
$ reggi --echo "hello" --echo "world"
hello
world

正如您所见,引用的参数不被尊重,因为它们应该是 `"hello world" 应该正确回显。

这是脚本,问题在 while 循环内。

如何解析这些标志,并保持将带引号的参数传递给函数?

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
STR="$(find $DIR/scripts -type f -name '*.sh' -print)"
ARR=( $STR )
TUSAGE="\n"

for f in "${ARR[@]}"; do
    if [ -f $f ]
    then
        . $f --source-only

        if [ -z "$USAGE" ]
        then
            :
        else
            TUSAGE="$TUSAGE \t$USAGE\n"
        fi

        USAGE=""
    else
        echo "$f not found"
    fi
done 

TUSAGE="$TUSAGE \t--help (shows this help output)\n"

function usage() {
  echo "Usage: --function <args> [--function <args>]"
  echo $TUSAGE
  exit 1
}

HELP=false

cmd=()
while [ $# -gt 0 ]; do                         # loop until no args left

    if [[ $1 = '--help' ]] || [[ $1 = '-h' ]] || [[ $1 = '--h' ]] || [[ $1 = '-help' ]]; then
        HELP=true
    fi

    if [[ $1 = --* ]] || [[ $1 = -* ]]; then                    # arg starts with --
        if [[ ${#cmd[@]} -gt 0 ]]; then
            "${cmd[@]}"
        fi
        top=`echo $1 | tr -d -`                              # remove all flags
        top=`echo ${top:0:1} | tr  '[a-z]' '[A-Z]'`${top:1}  # make sure first letter is uppercase
        top=reggi$top                                         # prepend reggi
        cmd=( "$top" )                                       # start new array
    else
        echo $1
        cmd+=( "$1" )
    fi
    shift
done

if [[ "$HELP" = true ]]; then
    usage
elif [[ ${#cmd[@]} -gt 0 ]]; then
    ${cmd[@]}
else
    usage
fi

【问题讨论】:

  • 如果您可以像ARR=( $STR ) 那样使用分词安全地填充数组,那么您一开始就不需要数组。
  • 第一个ARR=( $STR )只是为了拆分文件以便我可以获取它们,这不是参数解析部分。
  • 仍然是错误的,如果它与您实际询问的内容无关,则不属于问题。
  • 添加一个shebang,然后将您的脚本粘贴到那里:shellcheck.net
  • 你用shell标记了这个问题。请注意,posix shell 没有数组。

标签: bash shell terminal command-line-interface


【解决方案1】:

在这个脚本中有很多地方都有变量引用,但它们周围没有双引号。这意味着变量的值将受到吐字和通配符扩展的影响,这可能会产生各种奇怪的效果。

您看到的具体问题是由于倒数第四行 ${cmd[@]} 上未加引号的变量引用。使用cmd=( echo "hello world" ),分词使这相当于echo hello world,而不是echo "hello world"

修复这一行将解决您当前的问题,但还有许多其他未加引号的变量引用可能会在以后导致其他问题。我建议修复所有。 Cyrus 对shellcheck.net 的推荐很好地指出了它们,并且还会指出其他一些我不会在这里讨论的问题。它不会提到的一件事是您应该避免使用全大写变量名(DIRTUSAGE 等)——有一堆具有特殊含义的全大写变量,很容易意外重用一个并以奇怪的效果结束。小写和大小写混合的变量更安全。

我还建议不要在字符串中使用\t\n,而是指望echo 将它们分别转换为制表符和换行符。 echo 的某些版本会自动执行此操作,有些需要-e 选项来告诉他们这样做,有些会打印“-e”作为其输出的一部分……这是一团糟。在 bash 中,您可以使用 $'...' 直接翻译这些转义序列,例如:

tusage="$tusage"$' \t--help (shows this help output)\n'    # Note mixed quoting modes
echo "$tusage"    # Note that double-quoting is *required* for this to work right

您还应该修复文件列表,使其不依赖于未引用(参见 chepner 的评论)。如果您不需要扫描 $DIR/scripts 的子目录,您可以使用一个简单的通配符来执行此操作(注意小写 vars 并且 var 是双引号,但通配符不是):

arr=( "$dir/scripts"/*.sh )

如果您需要查看子目录,那就更复杂了。如果您有 bash v4,则可以使用 globstar 通配符,如下所示:

shopt -s globstar
arr=( "$dir/scripts"/**/*.sh )

如果您的脚本可能必须在 bash v3 下运行,请参阅 BashFAQ #20: "How can I find and safely handle file names containing newlines, spaces or both?",或者直接使用:

while IFS= read -r -d '' f <&3; do
    if [ -f $f ]
        # ... etc
done 3< <(find "$dir/scripts" -type f -name '*.sh' -print0)

(这是我最喜欢的 it-just-works 习惯用法,用于迭代 find 的匹配项。虽然它确实需要 bash,而不是一些通用的 POSIX shell。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-31
    • 1970-01-01
    • 1970-01-01
    • 2015-11-21
    • 2014-04-13
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多