我知道,这被问到了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
在这里是假名,它实际上可以是任何东西。
感谢。
答案 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=
表达式所以输出将是:
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或其他任何方法来解决错误的拆分,您将遇到同样的问题。不要在源头后面重新发明轮子或解决问题。如果您的输入不能被信任,那么在错误的拆分中也不能被信任。无论哪种方式,如果您无法信任输入,您都必须对其进行验证。