Bash 引用数组扩展
Posted
技术标签:
【中文标题】Bash 引用数组扩展【英文标题】:Bash quoted array expansion 【发布时间】:2012-10-10 17:06:56 【问题描述】:当我编写一个 bash 程序时,我通常会构造如下调用:
declare -a mycmd=( command.ext "arg1 with space" arg2 thing etc )
"$mycmd[@]" || echo "Failed: foo"
其中die foo
是一个bash 函数,它打印Error foo
并退出。
但是如果我想清楚错误原因,我想打印失败的命令:
"$mycmd[@]" || echo "Failed: foo: $mycmd[*]"
所以用户可以运行 dead 命令并找出为什么。但是,在此过程中引用丢失了 - 包含空格或转义字符的 Failed 消息参数不会以可以剪切-n-粘贴和运行的方式打印。
有没有人建议用一种紧凑的方法来解决这个问题?
我认为问题在于 bash 处理命令参数解析的方式,以及(内置)echo 处理参数的方式。说明问题的另一种方式是:
我如何打印以下 bash 示例中带空格的参数的引号(必须作为脚本运行,而不是立即模式):
#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "$myargs[@]"
ls
echo "$myargs[@]"
实际结果:
1 2 3 4
1 2 3 4
想要的结果
1 2 3 4
1 2 "3 4"
或
1 2 3 4
"1" "2" "3 4"
在几个额外的 bash 代码字符中。
问题已结束:@camh 精彩回答:
更新脚本:
#!/bin/bash
mkdir qatest; cd qatest
declare -a myargs=(1 2 "3 4")
touch "$myargs[@]"
ls
echo "$myargs[@]"
echo $(printf "'%s' " "$myargs[@]")
输出:
1 2 3 4
1 2 3 4
'1' '2' '3 4'
【问题讨论】:
您应该更喜欢printf '%q'
或 Bash 4.4 的 "$mycmd[@]@Q"
扩展而不是 printf "'%s'"
。在许多情况下,像这样的幼稚引用会失败,例如myargs=(1 2 "3' 4")
.
【参考方案1】:
另一种方法
# echo_array.sh
contains_space() [[ "$1" =~ " " ]]; return $?;
maybe_quote_one() contains_space "$1" && echo \'"$1"\' || echo "$1";
maybe_quote() while test "$1"; do maybe_quote_one "$1"; shift; done;
arridx() echo '$'$1'['$2']';
arrindir() echo $(eval echo `arridx $1 $2`);
arrsize() echo `eval echo '$'#$1'[@]'`;
echo_array()
echo -n "$1=( "
local i=0
for (( i=0; i < `arrsize a`; i++ )); do
echo -n $(maybe_quote "$(arrindir $1 $i)") ''
done
echo ")"
用法:
source echo_array.sh
a=( foo bar baz "It was hard" curious '67 - 00' 44 'my index is 7th' )
echo_array a
# a=( foo bar baz 'It was hard' curious '67 - 00' 44 'my index is 7th' )
arrindir a 7
# my index is 7th
arrindir a 0
# foo
arrsize a
# 8
$ bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
【讨论】:
【参考方案2】:您的问题在于echo
。它正在获取正确数量的参数,其中一些参数包含空格,但它的输出失去了参数之间的空格和参数内的空格的区别。
相反,您可以使用printf(1)
输出参数并始终包含引号,利用 printf 的功能,当格式字符串中的参数多于格式说明符时,将格式字符串连续应用于参数:
echo "Failed: foo:" $(printf "'%s' " "$mycmd[@]")
即使不需要,也会在每个参数周围加上单引号:
Failed: foo: 'command.ext' 'arg1 with space' 'arg2' 'thing' 'etc'
我使用单引号来确保其他 shell 元字符不会被错误处理。这将适用于除单引号本身之外的所有字符 - 即,如果您有一个包含单引号的参数,则上述命令的输出将无法正确剪切和粘贴。这可能是您最接近而又不会弄乱的地方。
编辑:大约 5 年后,自从我回答了这个问题以来,bash 4.4 已经发布。这有 "$var@Q"
扩展,它引用变量以便它可以被 bash 解析。
这将这个答案简化为:
echo "Failed: foo: " "$mycmd[@]@Q"
这将正确处理参数中的单引号,而我的早期版本没有。
【讨论】:
对于所有寻找该 bash 4.4 功能文档的人:lwn.net/Articles/701009 在下面讨论了可能的用例,我发现这些用例很有见地。我要感谢@camh,您足够彻底地使用 bash 4.4 功能跟进此问题。如果我此时没有发现这一点,您可能稍后为我节省了大量的重写!谢谢!【参考方案3】:我喜欢将代码放在一个函数中,这样更容易重用和记录:
function myjoin
local list=("$@")
echo $(printf "'%s', " "$list[@]")
declare -a colorlist=()
colorlist+=('blue')
colorlist+=('red')
colorlist+=('orange')
echo "[$(myjoin $colorlist[@])]"
请注意我是如何在解决方案中添加逗号的,因为我正在使用 bash 脚本生成代码。
[编辑:修复 EM0 指出它正在返回 ['blue',]]的问题
【讨论】:
这会为我返回['blue',]
。【参考方案4】:
一个繁琐的方法(只引用包含空格的参数):
declare -a myargs=(1 2 "3 4")
for arg in "$myargs[@]"; do
# testing if the argument contains space(s)
if [[ $arg =~ \ ]]; then
# enclose in double quotes if it does
arg=\"$arg\"
fi
echo -n "$arg "
done
输出:
1 2 "3 4"
顺便说一句,关于quoting is lost on this pass
,请注意引号从不保存。 " "
是一个特殊字符,它告诉 shell 将内部的任何内容视为单个字段/参数(即不拆分它)。另一方面,文字引号(像 \"
这样的类型)被保留。
【讨论】:
【参考方案5】:bash 的 printf 命令具有 %q 格式,可在打印字符串时为其添加适当的引号:
echo "Failed: foo:$(printf " %q" "$mycmd[@]")"
请注意,它对引用某些内容的“最佳”方式的想法并不总是与我的相同,例如它倾向于转义有趣的字符而不是将字符串括在引号中。例如:
crlf=$'\r\n'
declare -a mycmd=( command.ext "arg1 with space 'n apostrophe" "arg2 with control$crlf characters" )
echo "Failed: foo:$(printf " %q" "$mycmd[@]")"
打印:
Failed: foo: command.ext arg1\ with\ space\ \'n\ apostrophe $'arg2 with control\r\n characters'
【讨论】:
“转义搞笑字符”的实现逻辑比引用简单。【参考方案6】:declare -p quotedarray
怎么样?
-- 编辑--
实际上,declare -p quotedarray
会很好地满足您的目的。如果您坚持结果的输出格式,那么我有一个小技巧可以完成这项工作,但只是针对索引数组而不是关联数组。
declare -a quotedarray=(1 2 "3 4")
temparray=( "$quotedarray[@]/#/\"" ) #the outside double quotes are critical
echo $temparray[@]/%/\"
【讨论】:
收益:declare -a quotedarray='([0]="1" [1]="2" [2]="3" [3]="4")'
。对我来说似乎没有帮助。
@AlexBrown 你确定你的结果吗?请再试一次。如果您以这种方式定义数组declare -a quotedarray=(1 2 "3 4")
,则无法获得结果
虽然你提出了一个有趣的观点,但我的例子似乎有点错误。
@AlexBrown 如果得到结果declare -a quotedarray='([0]="1" [1]="2" [2]="3" [3]="4")'
,那么quotedarray 必须定义为(1 2 3 4)
,包含四个元素。请仔细检查数组,或取消设置并重试。
感谢您指出问题,原来我需要一个 bash 脚本 来显示问题,所以我修改了示例。以上是关于Bash 引用数组扩展的主要内容,如果未能解决你的问题,请参考以下文章