如何在 shell 脚本中拆分引用的参数

How to split quoted argument in shell script

我正在编写 shell 脚本。它的参数的个数是一个,唯一的参数是一个包含空格的双引号字符串,如下所示:

$ ./test.sh "'a bcd   e' 'f ghi' 'jkl mn'"

(这些是必需的规格,无法更改。)

我想为上述输入获得以下输出。

a bcd   e
f ghi
jkl mn

但是,我无法使用简单的 for 循环得到这个结果。

如果Bash

shell脚本源

#!/bin/bash
for STR in ; do
    echo $STR
done

结果

$ ./test.sh "'a bcd   e' 'f ghi' 'jkl mn'"
'a
bcd
e'
'f
ghi'
'jkl
mn'

如果是 Zsh

shell脚本源

#!/bin/zsh
for STR in ; do
    echo $STR
done

结果

$ ./test.sh "'a bcd   e' 'f ghi' 'jkl mn'"
'a bcd   e' 'f ghi' 'jkl mn'

如何获得预期的输出?

使用 xargs 拆分引用的参数并 运行 使用拆分参数再次编写脚本:

#!/usr/bin/env sh

# If there is only one argument
if  [ "$#" -eq 1 ]; then
  # Use xargs to split arguments
  # and run itself with prepended dummy _ argument
  printf '%s' "" | xargs "[=10=]" _
fi

# Remove/ignore prepend dummy argument
shift

i=0
for arg; do
  i=$((i + 1))
  printf 'Arg %d: %s\n' "$i" "$arg"
done

带有示例数据的脚本输出:

$ sh a.sh "'a bcd   e' 'f ghi' 'jkl mn'"
Arg 1: a bcd   e
Arg 2: f ghi
Arg 3: jkl mn

参数拆分和自我调用的一个非常简短的版本:

# Split quoted arguments and call self
[ "" ]||printf '%s' ""|xargs "[=12=]" _;shift
# Process normal arguments as with any shell script

另一种处理方法是将 awk' 一起用作 field-separator,然后丢弃空字段输出 non-empty 字段(分隔的参数) .

例如,您可以这样做:

#!/bin/bash

awk -F "'" '{
  for (i=1; i<=NF; i++) {
    match ($i, /[^[:blank:]]/)
    if (RSTART >0)
      print $i
  }
}' <<< 

awk 脚本遍历由 ' 分隔的每个字段并使用 match() 检查是否存在 non-blank 字符。 (match()RSTART 设置为第一个 non-blank 的基于 1 的索引,因此测试 RSTART > 0 是否告诉您 non-blank 存在)

例子Use/Output

$ bash test.sh "'a bcd   e' 'f ghi' 'jkl mn'"
a bcd   e
f ghi
jkl mn

如果您愿意,可以使用 mapfile -t 将上面的 awk 命令包装在 进程替换 中,以将单独的参数存储在一个数组中,如果您需要保留它们以供日后调用。

因为 awk 不依赖于你拥有的 shell,这对所有 shell 都是可移植的(你必须为那些 shell 传递参数没有 herestring 支持。)

内嵌引号失去了 Shell

的意义

我知道您说过您一直坚持以这种方式为脚本提供参数。知道那是错误的方法。这是一个 BashFAQ #50 问题。 double-quotes 中的 single-quotes 失去了作为特殊分隔符的任何意义,仅被视为嵌入的 ASCII single-quote。这使使用 command-line.

变得复杂

强制 command-line 工作不是解决方案,它是 Work-Around。解决方案是修复向脚本提供参数的方式。

您可以使用 bash 正则表达式:

#!/usr/bin/env bash

string=
pattern="[^']*'([^']+)'"

while [[ $string =~ $pattern ]]; do
    echo "Current argument : ${BASH_REMATCH[1]}"
    string=${string:${#BASH_REMATCH[0]}}
done

运行 与 :

./test.sh "'a bcd   e' 'f ghi' 'jkl mn'"

zsh 的版本:

#!/usr/bin/env zsh
for str in ${(M)${(ps"'")argv}:#*[! ]*}; do
    print $str
done

这使用 zsh 扩展将输入拆分为 ' 上的数组,然后删除数组中仅包含空格的所有成员。

./test.zsh "'a bcd   e' 'f ghi' 'jkl mn'"  " '  leading space'"
a bcd   e
f ghi
jkl mn
  leading space