如何正确地从bash函数调用中返回多行字符串?

21 浏览
0 Comments

如何正确地从bash函数调用中返回多行字符串?

我一直在bash中使用多行字符串(不需要提到bash数组,这是一个POSIX的问题)。在线的BASH模拟器上发布了一个完整的工作演示

我遇到的问题是,每次调用一个函数并返回一个字符串时,正确处理多行字符串的方法无意中导致字符串结尾添加了一个额外的chr(10)。

建议的重复内容不适用:

这个工作示例中的多行bash字符串的正确表示形式是:

# 声明为多行字符串的bash变量
ini_buffer="1.1.1.1"
"

将原始的多行字符串转换为长度为8的十六进制转储:

00000000  31 2e 31 2e 31 2e 31 0a  00                       |1.1.1.1..|
00000009

Bash脚本开始如下:

# ini_buffer的末尾正好有一个chr(10)
ini_buffer="1.2.3.4
"
echo "initial ini_buffer \"\"\"$ini_buffer\"\"\""
echo "initial ini_buffer len: ${#ini_buffer}"
echo
IFS= read -rd '' result < <(echo "$ini_buffer")
# 最终输出前面附加了一个SECOND chr(10)
echo "result len: ${#result}"
echo "\"\"\"$result\"\"\""
echo

结果如下:

initial ini_buffer """1.2.3.4
"""
initial ini_buffer len: 8
result len: 9
"""1.2.3.4
"""

注意它增加了一个字符!

00000000 31 2e 31 2e 31 2e 31 0a 0a 00 |1.1.1.1..|

00000009

添加了第一个函数:

first_function()
{
  local first_buffer result1_buffer
  # 以单个chr(10)的字符串作为参数
  first_buffer="$1"
  # 调用一个不做任何操作的函数
  IFS= read -rd '' result1_buffer < <(second_function "$first_buffer")
  # 结果前面又附加了一个chr(10),总共有两个chr(10)
echoerr "result1_buffer len: ${#result1_buffer}"
echoerr "\"\"\"$result1_buffer\"\"\""
  # 唯一一种整洁地返回多行字符串的方法是通过标准输出(fd=1)
  # echo "first_buffer len: ${#first_buffer}"
  echo "$result1_buffer"
}
# ini_buffer的末尾正好有一个chr(10)
ini_buffer="1.2.3.4
"
echo "initial ini_buffer \"\"\"$ini_buffer\"\"\""
echo "initial ini_buffer len: ${#ini_buffer}"
echo
# 将`echo`改为`first_function`
IFS= read -rd '' result < <(first_function "$ini_buffer")
# 最终输出前面附加了一个SECOND chr(10),总共有3个附加的chr(10)
echo "result len: ${#result}"
echo "\"\"\"$result\"\"\""
echo

第一个函数的结果是:

initial ini_buffer """1.2.3.4
"""
initial ini_buffer len: 8
result1_buffer len: 9
"""1.2.3.4
"""
result len: 10
"""1.2.3.4
"""

每次从嵌套调用的函数返回时,都会附加一个chr(10)

当引入第二个函数时,这个问题还会增加,为了简洁起见,我不在这里包含第二个函数。

这对我来说非常令人恼火。这与最后一行是空行(或只是一个chr(10)字符)有关。关于正确处理多行字符串的权威内容不多。

我做错了什么?

这里使用了过程替换(<( ... )),而不是通常的命令替换($( ... )),因为通常的命令替换在处理多行字符串时存在困难。因此,我必须使用以下方式将任何调试语句输出到STDERR:

echoerr() { printf "%s\n" "$*" >&2; }

我想正确处理多行字符串,特别是在字符串末尾有空行的情况下。

这里重新重申了一个完整的测试(工作演示链接在第一段中引用):

echoerr() { printf "%s\n" "$*" >&2; }
dump_string_char()
{
  local string len_str idx this_char this_int
  string="$1"
  echoerr "string: \"\"\"$string\"\"\"" 
  len_str="${#string}"
  idx=0
  while [ $idx -lt ${len_str} ]; do
    this_char="${string:$idx:1}"
    this_int="$(LC_CTYPE=C printf "%d" "'$this_char")"
    echoerr "idx: $idx"
    if [ $this_int -lt 32 ]; then
      echoerr "$idx: ${this_int}" 
    else
      echoerr "$idx: \"${this_char}\"" 
    fi
    ((idx++))
  done
}
second_function()
{
  local second_ini_buffer result2_buffer
  second_ini_buffer="$1"
  # 一些神奇的awk/sed,未匹配任何模式
  # 所以让我们使用'echo'重新保存相同的字符串
  IFS= read -rd '' result2_buffer < <(echo "$second_ini_buffer")
  echoerr "result2_buffer len: ${#result2_buffer}"
  echoerr "\"\"\"$result2_buffer\"\"\""
  # 所以按原样传回完整的ini_buffer。
  # 希望末尾只有一个chr(1)。
  # 但实际上不是这样的。
  echo "$result2_buffer"
}
first_function()
{
  local first_buffer result1_buffer
  # 以单个chr(10)的字符串作为参数
  first_buffer="$1"
  # 调用一个不做任何操作的函数
  IFS= read -rd '' result1_buffer < <(second_function "$first_buffer")
  # IFS= read -rd '' result1_buffer < <(echo "$first_buffer")
  # 结果前面又附加了一个chr(10),总共有两个chr(10)
echoerr "result1_buffer len: ${#result1_buffer}"
echoerr "\"\"\"$result1_buffer\"\"\""
  # 唯一一种整洁地返回多行字符串的方法是通过标准输出(fd=1)
  # echo "first_buffer len: ${#first_buffer}"
  echo "$result1_buffer"
}
# ini_buffer的末尾正好有一个chr(10)
ini_buffer="1.2.3.4
"
echoerr "initial ini_buffer \"\"\"$ini_buffer\"\"\""
echoerr "initial ini_buffer len: ${#ini_buffer}"
echoerr
dump_string_char "$ini_buffer"
IFS= read -rd '' result < <(first_function "$ini_buffer")
# 最终输出前面附加了一个SECOND chr(10),总共有3个附加的chr(10)s
echoerr "result len: ${#result}"
echoerr "\"\"\"$result\"\"\""
echoerr

再次向认为这些是同一个问题的主持人解释:

它与回车无关,而是与许多ASCII字符有关,因此这不适用:

而且它没有解决这个问题中涉及的空白多行,所以也不适用:

0
0 Comments

问题出现的原因是在使用echo命令时,它会在字符串的末尾添加一个换行符,如果字符串本身已经包含一个换行符,那么最终的字符串将会有两个换行符。由于每次都是使用echo "$result"来“返回”字符串,所以每次都会添加一个新的换行符。

解决方法是使用printf "%s" "$result"来替代echo "$result":

ini_buffer="1.2.3.4
"
echo "initial ini_buffer len: ${#ini_buffer}"
IFS= read -rd '' result < <(printf "%s" "$ini_buffer")
echo "result len: ${#result}"

结果:

initial ini_buffer len: 8
result len: 8

另一种选择是使用echo -ne "stuff",但是printf(对于所有输出)是更好的选择。

这个答案看起来是迄今为止最好的答案;早先的评论中提到使用print "$var"看起来很有吸引力,直到我发现了其中的风险($var可能包含嵌入在其中的printf格式字段选项)。这个答案确保其输出尽可能“原样”和逐字输出。

这是正确的答案。在这个链接的在线bash模拟器上演示了这一点: ideone.com/wzkLCW

0