当我写一个bash程序时,我通常构造如下调用:
declare -a mycmd=( command.ext "arg1 with space" arg2 thing etc )
"${mycmd[@]}" || echo "Failed: foo"
其中die foo
是打印Error foo
并退出的bash函数。
但如果我想清楚错误原因,我想打印失败的命令:
"${mycmd[@]}" || echo "Failed: foo: ${mycmd[*]}"
因此用户可以运行dead命令并找出为什么。但是,在此传递中引用丢失 - 具有空格或转义字符的失败消息参数不会以可以剪切和粘贴的方式打印。
有没有人建议采用紧凑的方法来解决这个问题?
我认为问题是bash处理命令的参数解析的方式,以及echo(内置)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"
OR
1 2 3 4
"1" "2" "3 4"
少数额外的bash代码字符。
更新的脚本:
#!/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'
答案 0 :(得分:22)
您的问题出在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}"
这将正确处理参数中的单引号,我的早期版本没有。
答案 1 :(得分:12)
bash的printf命令有一个%q格式,可以在字符串打印时为其添加适当的引号:
echo "Failed: foo:$(printf " %q" "${mycmd[@]}")"
请注意,它最好的"#34;引用某事的方式并不总是与我的相同,例如:它往往倾向于转义有趣的字符而不是用引号括起字符串。例如:
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'
答案 2 :(得分:1)
declare -p quotedarray
怎么样?
- 编辑 -
实际上,declare -p quotedarray
会很好地满足您的目的。如果你坚持结果的输出格式,那么我有一个小技巧可以完成这项工作,但只是因为索引数组不是关联的。
declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}
答案 3 :(得分:1)
一种繁琐的方法(仅引用包含空格的参数):
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将内部的任何内容视为单个字段/参数(即不将其拆分)。另一方面,保留字面引号(类似于此\"
)。
答案 4 :(得分:0)
# 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.
答案 5 :(得分:-1)
我喜欢将代码放在函数中,因此更容易重用和记录:
function myjoin
{
local list=("${@}")
echo $(printf "'%s', " "${list[@]}")
}
declare -a colorlist=()
colorlist+=('blue')
colorlist+=('red')
colorlist+=('orange')
echo "[$(myjoin ${colorlist[@]})]"
请注意我是如何在解决方案中添加逗号的,因为我使用bash脚本生成代码。
[编辑:解决EM0指出它正在返回['blue',]]
的问题