我怎么能有一个换行在sh字符串?

STR="Hello\nWorld"
echo $STR

作为输出产生

Hello\nWorld

而不是

Hello
World

我应该做什么有一个换行在字符串?

注意:这个问题不是关于回声。 我知道echo -e,但我正在寻找一个解决方案,允许将一个字符串(包括换行符)作为参数传递给其他命令,这些命令没有类似的选项来将\n解释为换行符

420226 次浏览

问题不在于外壳。问题实际上出在echo命令本身,以及变量插值前后缺少双引号。你可以尝试使用echo -e,但并非所有平台都支持它,这也是现在推荐使用printf的原因之一。

您还可以尝试直接在shell脚本中插入换行符(如果您正在编写脚本),因此它看起来像…

#!/bin/sh
echo "Hello
World"
#EOF

或者同样的

#!/bin/sh
string="Hello
World"
echo "$string"  # note double quotes!

如果你在使用Bash,你可以在特殊引用的$'string'中使用反斜杠转义。例如,添加\n:

STR=$'Hello\nWorld'
echo "$STR" # quotes are required here!

打印:

Hello
World

如果你使用的是其他shell,只需在字符串中插入换行符:

STR='Hello
World'

Bash识别出$''字符串中的许多其他反斜杠转义序列。以下是Bash手册页面的节选:

Words of the form $'string' are treated specially. The word expands to
string, with backslash-escaped characters replaced as specified by the
ANSI C standard. Backslash escape sequences, if present, are decoded
as follows:
\a     alert (bell)
\b     backspace
\e
\E     an escape character
\f     form feed
\n     new line
\r     carriage return
\t     horizontal tab
\v     vertical tab
\\     backslash
\'     single quote
\"     double quote
\nnn   the eight-bit character whose value is the octal value
nnn (one to three digits)
\xHH   the eight-bit character whose value is the hexadecimal
value HH (one or two hex digits)
\cx    a control-x character


The expanded result is single-quoted, as if the dollar sign had not
been present.


A double-quoted string preceded by a dollar sign ($"string") will cause
the string to be translated according to the current locale. If the
current locale is C or POSIX, the dollar sign is ignored. If the
string is translated and replaced, the replacement is double-quoted.

Echo是90年代的,而且充满了危险,使用它应该会导致内核转储不少于4GB。严肃地说,echo的问题是Unix标准化进程最终发明printf实用程序的原因,解决了所有问题。

要在字符串中获取换行符,有两种方法:

# 1) Literal newline in an assignment.
FOO="hello
world"
# 2) Command substitution.
BAR=$(printf "hello\nworld\n") # Alternative; note: final newline is deleted
printf '<%s>\n' "$FOO"
printf '<%s>\n' "$BAR"

在那里!没有SYSV和BSD的回声疯狂,所有东西都被整齐地打印出来,并完全支持C转义序列。请大家现在使用printf来满足所有的输出需求,不要回头。

我根据其他答案所做的是

NEWLINE=$'\n'
my_var="__between eggs and bacon__"
echo "spam${NEWLINE}eggs${my_var}bacon${NEWLINE}knight"


# which outputs:
spam
eggs__between eggs and bacon__bacon
knight

我发现-e标志优雅而直接

bash$ STR="Hello\nWorld"


bash$ echo -e $STR
Hello
World

如果字符串是另一个命令的输出,我只使用引号

indexes_diff=$(git diff index.yaml)
echo "$indexes_diff"
  1. 唯一简单的替代方法是在变量中输入一个新行:

    $ STR='new
    line'
    $ printf '%s' "$STR"
    new
    line
    

是的,这意味着在代码中需要的地方编写输入

  1. new line字符有几个等价物。

    \n           ### A common way to represent a new line character.
    \012         ### Octal value of a new line character.
    \x0A         ### Hexadecimal value of a new line character.
    

但所有这些都需要“解释”。通过一些工具(POSIX printf)

    echo -e "new\nline"           ### on POSIX echo, `-e` is not required.
printf 'new\nline'            ### Understood by POSIX printf.
printf 'new\012line'          ### Valid in POSIX printf.
printf 'new\x0Aline'
printf '%b' 'new\0012line'    ### Valid in POSIX printf.

因此,这个工具需要用new-line来构建一个字符串:

    $ STR="$(printf 'new\nline')"
$ printf '%s' "$STR"
new
line
    在某些shell中,序列是一个特殊的shell扩展。 已知在ksh93, bash和zsh中工作:

    $ STR=$'new\nline'
    
  1. 当然,更复杂的解决方案也是可能的:

    $ echo '6e65770a6c696e650a' | xxd -p -r
    new
    line
    

    $ echo "new line" | sed 's/ \+/\n/g'
new
line

我不是bash专家,但这个对我来说很管用:

STR1="Hello"
STR2="World"
NEWSTR=$(cat << EOF
$STR1


$STR2
EOF
)
echo "$NEWSTR"

我发现这更容易格式化文本。

在单引号'…\n…,但是双引号不能用。

$ echo $'Hello\nWorld'
Hello
World
$ echo $"Hello\nWorld"
Hello\nWorld

在我的系统(Ubuntu 17.10)上,你的例子就像预期的那样工作,无论是从命令行输入(到sh)还是作为sh脚本执行:

[bash]§ sh
$ STR="Hello\nWorld"
$ echo $STR
Hello
World
$ exit
[bash]§ echo "STR=\"Hello\nWorld\"
> echo \$STR" > test-str.sh
[bash]§ cat test-str.sh
STR="Hello\nWorld"
echo $STR
[bash]§ sh test-str.sh
Hello
World

我想这回答了你的问题:它就是有效。(我没有试图弄清楚细节,比如在sh中,换行符替换\n的具体时间)。

然而,我注意到这个脚本在执行bash时表现不同,而是会打印出Hello\nWorld:

[bash]§ bash test-str.sh
Hello\nWorld

我已经设法用bash获得所需的输出,如下所示:

[bash]§ STR="Hello
> World"
[bash]§ echo "$STR"

注意$STR周围的双引号。如果保存并作为bash脚本运行,则行为相同。

下面也给出了期望的输出:

[bash]§ echo "Hello
> World"

那些只需要换行符而不喜欢破坏缩进的多行代码的挑剔的人可以这样做:

IFS="$(printf '\nx')"
IFS="${IFS%x}"

Bash(可能还有其他shell)在命令替换之后会吞噬所有尾随的换行符,所以你需要用一个非换行符结束printf字符串,然后删除它。这也可以很容易地成为一个联机程序。

IFS="$(printf '\nx')" IFS="${IFS%x}"

我知道这是两个动作而不是一个,但我的缩进和可移植性强迫症现在是和平的:)我最初开发这个是为了能够分割换行分离输出,最终使用了使用\r作为结束字符的修改。这使得换行分割甚至适用于以\r\n结尾的dos输出。

IFS="$(printf '\n\r')"

我对这里的选择都不太满意。这对我来说很管用。

str=$(printf "%s" "first line")
str=$(printf "$str\n%s" "another line")
str=$(printf "$str\n%s" "and another line")

这并不理想,但我已经编写了大量代码,并以类似于问题中使用的方法定义了字符串。接受的解决方案需要我重构大量的代码,所以相反,我用"$'\n'"替换了每个\n,这对我有用。

免责声明:我首先写了这篇文章,然后偶然发现了这个问题。我以为这个解决方案还没有发布,看到tlwhitec确实发布了一个类似的答案。我仍然把这个贴出来,因为我希望这是一个有用和彻底的解释。

简短的回答:

这似乎是一个相当可移植的解决方案,因为它可以在相当多的shell上工作(参见评论)。
这样你就可以在变量中得到一个真正的换行符 这个解决方案的好处是你不必在源代码中使用换行符,所以你可以缩进 按照您想要的方式编写代码,解决方案仍然有效。这使得它健壮。

# Robust way to put a real newline in a variable (bash, dash, ksh, zsh; indentation-resistant).
nl="$(printf '\nq')"
nl=${nl%q}

再答:

以上方案说明:

换行符通常会因为命令替换而丢失,但是为了防止这种情况,我们添加了一个'q',然后删除它。(使用双引号的原因将在下文进一步解释。)

我们可以证明变量包含一个实际的换行符(0x0A):

printf '%s' "$nl" | hexdump -C
00000000  0a  |.|
00000001

(注意,'%s'是需要的,否则printf将把一个字面的'\n'字符串转换为一个实际的0x0A字符,这意味着我们什么都不会证明。)

当然,除了这个答案中提出的解决方案,人们也可以使用这个(但是…):

nl='
'

... 但是这种方法的健壮性较差,并且很容易因为不小心缩进代码,或者后来忘记缩进代码而被破坏,这使得在(缩进的)函数中使用不方便,而早期的解决方案是健壮的。

现在,对于双引号:
nl="$(printf '\nq')"中使用双引号"包围命令替换的原因是,你甚至可以用local关键字或内置(如在函数中)作为变量赋值的前缀,并且它仍然可以在所有shell上工作,然而,否则dash shell就会遇到麻烦,从某种意义上说,破折号会失去'q',你最终会得到一个空的'nl'变量(同样,由于命令替换)。
这个问题可以用另一个例子更好地说明:

dash_trouble_example() {
e=$(echo hello world) # Not using 'local'.
echo "$e" # Fine. Outputs 'hello world' in all shells.


local e=$(echo hello world) # But now, when using 'local' without double quotes ...:
echo "$e" # ... oops, outputs just 'hello' in dash,
# ... but 'hello world' in bash and zsh.


local f="$(echo hello world)" # Finally, using 'local' and surrounding with double quotes.
echo "$f" # Solved. Outputs 'hello world' in dash, zsh, and bash.


# So back to our newline example, if we want to use 'local', we need
# double quotes to surround the command substitution:
# (If we didn't use double quotes here, then in dash the 'nl' variable
# would be empty.)
local nl="$(printf '\nq')"
nl=${nl%q}
}

上述解决方案的实际示例:

# Parsing lines in a for loop by setting IFS to a real newline character:


nl="$(printf '\nq')"
nl=${nl%q}


IFS=$nl


for i in $(printf '%b' 'this is line 1\nthis is line 2'); do
echo "i=$i"
done


# Desired output:
# i=this is line 1
# i=this is line 2


# Exercise:
# Try running this example without the IFS=$nl assignment, and predict the outcome.