44

当我编写一个 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'
4

6 回答 6

52

您的问题与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}"

这将正确处理参数中的单引号,而我的早期版本没有。

于 2012-10-20T04:48:59.367 回答
21

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'
于 2012-10-20T05:24:20.873 回答
2

怎么样declare -p quotedarray

- 编辑 -

实际上,declare -p quotedarray会很好地满足您的目的。如果您坚持结果的输出格式,那么我有一个小技巧可以完成这项工作,但只是针对索引数组而不是关联数组。

declare -a quotedarray=(1 2 "3 4")
temparray=( "${quotedarray[@]/#/\"}" ) #the outside double quotes are critical
echo ${temparray[@]/%/\"}
于 2012-10-20T04:12:31.950 回答
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 将内部的任何内容视为单个字段/参数(即不拆分它)。另一方面,文字引号(像这样键入\")被保留。

于 2012-10-20T05:40:08.113 回答
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 --版本

GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
于 2018-05-22T17:32:35.250 回答
-1

我喜欢将代码放在一个函数中,以便更容易重用和记录:

function myjoin
{
   local list=("${@}")
   echo $(printf "'%s', " "${list[@]}")
}

declare -a colorlist=()
colorlist+=('blue')
colorlist+=('red')
colorlist+=('orange')

echo "[$(myjoin ${colorlist[@]})]"

请注意我是如何在解决方案中添加逗号的,因为我正在使用 bash 脚本生成代码。

[编辑:修复 EM0 指出它正在返回 ['blue',]] 的问题

于 2015-06-27T02:40:14.477 回答