仅使用引号外的空格拆分字符串

时间:2012-10-10 14:05:30

标签: bash

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

我正在收到这样的字符串:

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在这里是假名,它实际上可以是任何东西。

感谢。

5 个答案:

答案 0 :(得分: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

这是否接近您的需求?

答案 1 :(得分: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

答案 2 :(得分:1)

它不是非常接近纯粹的bash - 但Python有一个shlex模块,它试图提供与shell兼容的lexing。

>>> 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[@]}"

答案 3 :(得分:0)

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

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

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

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

标准命令行解析将返回:

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

丢掉空的琴弦,你应该有你想要的东西。这是一个hacky(潜在的)解决方案,在使用类似的东西之前,我会敦促测试/思考它。

答案 4 :(得分:-1)

只需将eval与引号一起使用,并根据需要验证输入。如果您用bash或python或其他任何方法来解决错误的拆分,您将遇到同样的问题。不要在源头后面重新发明轮子或解决问题。如果您的输入不能被信任,那么在错误的拆分中也不能被信任。无论哪种方式,如果您无法信任输入,您都必须对其进行验证。