5

我知道,这被问了十亿次,但我还没有找到适合我的具体情况的最佳解决方案。

我收到这样的字符串:

VAR1="some text here" VAR2='some another text' some script --with --some=args

我如何像这样拆分字符串:(在纯 bash 中最可取)

VAR1="some text here"
VAR2='some another text'
some script --with --some=args

set -- $str导致VAR1="some

set -- "$str"返回整个字符串

eval set -- "$str"导致VAR1=some text here

当然,我可以在返回的字符串中添加引号eval,但是我得到了高度不可信的输入,所以eval根本不是一个选项。

重要提示:可以有从零到无限的 VAR,它们可以是单引号或双引号

另外,VAR这里是假名,实际上可以是任何名称。

谢谢。

4

4 回答 4

3

它远不接近纯shlexbash——但 Python 有一个模块,它试图提供与 shell 兼容的词法分析。

>>> import shlex, pprint
>>> pprint.pprint(shlex.split('''VAR1="some text here" VAR2='some another text' some script --with --some=args'''))
['VAR1=some text here',
 'VAR2=some another text',
 'some',
 'script',
 '--with',
 '--some=args']

以下更完整的示例使用 bash 中的这个 Python 模块,NUL 分隔的流提供明确的传输:

shlex() {
  python -c $'import sys, shlex\nfor arg in shlex.split(sys.stdin):\n\tsys.stdout.write(arg)\n\tsys.stdout.write(\"\\0\")'
}
args=()
while IFS='' read -r -d ''; do
  args+=( "$REPLY" )
done < <(shlex <<<$'VAR1="some text here" VAR2=\'some another text\' some script --with --some=args')
printf '%s\n' "${args[@]}"
于 2012-10-10T14:14:38.890 回答
3

呵呵,看来我参加聚会迟到了:)

这是我如何处理在脚本之前传递的环境变量。

首先,escape_args函数将转义“内部”传递的变量,

所以如果用户通过VAR="foo bar",它会变成VAR=foo\0040bar.

function escape_args {
  local str=''
  local opt=''
  for c in $1; do
    if [[ "$c" =~ ^[[:alnum:]]+=[\"|\'] ]]; then
      if [[ "${c: -1}" =~ [\"|\']  ]]; then
        str="$str $( echo $c | xargs )"
      else
        # first opt chunk
        # entering collector
        opt="$c"
      fi
    else
      if [ -z "$opt" ]; then
        # not inside collector
        str="$str $c"
      else
        # inside collector
        if [[ "${c: -1}" =~ [\"|\']  ]]; then
          # last opt chunk
          # adding collected chunks and this last one to str
          str="$str $( echo "$opt\0040$c" | xargs )"
          # leaving collector
          opt=''
        else
          # middle opt chunk
          opt="$opt\0040$c"
        fi
      fi
    fi
  done
  echo "$str"
}

让我们针对您输入的修改版本对其进行测试:

s="VAR1=\"some text here\" VAR2='some another text' VAR3=\"noSpaces\" VAR4='noSpacesToo' VAR5=noSpacesNoQuotes some script --with --some=args"

echo $(escape_args "$s")

VAR1=some\0040text\0040here VAR2=some\0040another\0040text VAR3=noSpaces VAR4=noSpacesToo VAR5=noSpacesNoQuotes some script --with --some=args

看,所有的变量都是空格转义和引号被删除,所以declare会正常工作。

现在您可以遍历输入的各个部分。

这是一个如何声明变量并运行脚本的示例:

cmd=''
for c in $(escape_args "$s"); do
  [[ "$c" =~ ^[[:alnum:]]+= ]] && declare "$(echo -e $c)" && continue
  cmd="$cmd $c"
done

echo VAR1 is set to $VAR1
echo VAR2 is set to $VAR2
echo VAR3 is set to $VAR3
echo VAR4 is set to $VAR4
echo VAR5 is set to $VAR5
echo $cmd

这个迭代器做了两件简单的事情:

  • SOME_VAR=如果块匹配表达式,则声明一个 var
  • 否则将块添加到最终 cmd

所以输出将是:

VAR1 is set to some text here
VAR2 is set to some another text
VAR3 is set to noSpaces
VAR4 is set to noSpacesToo
VAR5 is set to noSpacesNoQuotes
some script --with --some=args

这是否接近您的需求?

于 2012-10-10T20:26:22.447 回答
2

您可以使用以下纯 bash 代码。它逐个字符地遍历输入,并尝试保留有关在引号内/外的标志。

#! /bin/bash 
string=$(cat <<'EOF'
VAR1="some text here" VAR2='some another text' VAR3="a'b" VAR4='a"b' VAR5="a\"b" VAR6='a'"'"'b' some script --with --some=args
EOF
)
echo "$string"

results=()
result=''
inside=''
for (( i=0 ; i<${#string} ; i++ )) ; do
    char=${string:i:1}
    if [[ $inside ]] ; then
        if [[ $char == \\ ]] ; then
            if [[ $inside=='"' && ${string:i+1:1} == '"' ]] ; then
                let i++
                char=$inside
            fi
        elif [[ $char == $inside ]] ; then
            inside=''
        fi
    else
        if [[ $char == ["'"'"'] ]] ; then
            inside=$char
        elif [[ $char == ' ' ]] ; then
            char=''
            results+=("$result")
            result=''
        fi
    fi
    result+=$char
done
if [[ $inside ]] ; then
    echo Error parsing "$result"
    exit 1
fi

for r in "${results[@]}" ; do
    echo "< $r >"
done
于 2012-10-10T15:28:32.950 回答
0

您可以使用流编辑器来修改文本。您可以首先使用正则表达式获取变量,然后用空引号替换它们。在开头和结尾附加引号。在这个阶段你应该有:

VAR1="some text here" 
VAR2='some another text'

在单独的字符串中,原始字符串将如下所示:

"""""some script --with --some=args"

标准命令行解析将返回:

""
""
"some script --with --some=args"

扔掉空字符串,你应该有你想要的东西。这是一个 hacky(潜在)解决方案,我会敦促在使用类似的东西之前对其进行测试/思考。

于 2012-10-10T16:55:04.410 回答