如何在sh中的字符串中添加换行符?


How can I have a newline in a string in sh?

这个

1
2
3
STR="Hello
World"
echo $STR

作为输出生成

1
2
Hello
World

而不是

1
2
Hello
World

如果字符串中有换行符,我该怎么做?

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


解决方案是使用$'string',例如:

1
2
3
4
5
$ STR=$'Hello
World'
$ echo"$STR"
Hello
World

以下是bash手册页面的摘录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   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
         
     new line
         
     carriage return
          \t     horizontal tab
          \v     vertical tab
          \\     backslash
          \'     single quote
          "     double quote
         
nn   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
2
3
4
5
6
7
8
9
FOO="hello
world"
BAR=$(printf"hello
world
") # Alternative; note: final newline is deleted
printf '<%s>
'"$FOO"
printf '<%s>
'"$BAR"

那里!没有SysV和BSD的回声疯狂,一切得到整洁的打印和完全可移植的C转义序列支持。现在请大家使用printf,不要回头看。


我根据其他答案所做的是

1
2
3
4
5
6
7
8
9
NEWLINE=$'
'
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


问题不在外壳上。问题实际上在于echo命令本身,以及变量插值缺少双引号。您可以尝试使用echo -e,但并非所有平台都支持这一点,现在建议使用printf作为可移植性的原因之一。

您还可以尝试将换行符直接插入shell脚本(如果您正在编写的是脚本),这样看起来…

1
2
3
4
#!/bin/sh
echo"Hello
World"
#EOF

或等价地

1
2
3
4
#!/bin/sh
string="Hello
World"
echo"$string"  # note double quotes!

  • 唯一简单的选择是在变量中实际键入新行:

    1
    2
    3
    4
    5
    $ STR='new
    line'
    $ printf '%s'"$STR"
    new
    line

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

  • 有几个等同于new line字符。

    1
    2
    3
               ### 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)的"解释":

    1
    2
    3
    4
    5
    6
    7
    echo -e"new
    line"           ### on POSIX echo, `-e` is not required.
    printf 'new
    line'            ### Understood by POSIX printf.
    printf 'new\012line'          ### Valid in POSIX printf.
    printf 'new\x0Aline'      
    printf '%b' 'new\0012line'    ### Valid in POSIX printf.

    因此,需要使用该工具构建一个带有新行的字符串:

    1
    2
    3
    4
    5
    $ STR="$(printf 'new
    line')"
    $ printf '%s'"$STR"
    new
    line
  • 在某些shell中,序列$'是一个特殊的shell扩展。已知在KSH93、Bash和Zsh工作:

    1
    2
    $ STR=$'new
    line'
  • 当然,更复杂的解决方案也是可能的:

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

    1
    2
    3
    4
    $ echo"new line" | sed 's/ \+/
    /g'
    new
    line

  • 我觉得江户十一〔六〕旗优雅而笔直。

    1
    2
    3
    4
    5
    6
    bash$ STR="Hello
    World"

    bash$ echo -e $STR
    Hello
    World

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

    1
    2
    indexes_diff=$(git diff index.yaml)
    echo"$indexes_diff"


    A$位于单引号"……"之前,如下所示,但是双引号不起作用。

    1
    2
    3
    4
    5
    6
    7
    8
    $ echo $'Hello
    World'
    Hello
    World
    $ echo $"Hello
    World"
    Hello
    World


    我不是bash专家,但这个对我很有用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    STR1="Hello"
    STR2="World"
    NEWSTR=$(cat << EOF
    $STR1

    $STR2
    EOF
    )
    echo"$NEWSTR"

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


    我对这里的任何选择都不太满意。这就是我的工作。

    1
    2
    3
    4
    5
    str=$(printf"%s""first line")
    str=$(printf"$str
    %s""another line")
    str=$(printf"$str
    %s""and another line")


    那些挑剔的人只需要换行,轻视破坏缩进的多行代码,可以做到:

    1
    2
    3
    IFS="$(printf '
    x')"
    IFS="${IFS%x}"

    bash(以及可能的其他shell)在命令替换之后吞掉所有尾随的换行符,因此需要用非换行符结束printf字符串,然后删除它。这也很容易成为一条直线。

    1
    2
    IFS="$(printf '
    x')" IFS="${IFS%x}"

    I know this is two actions instead of one, but my indentation and portability OCD is at peace now :) I originally developed this to be able to split newline-only separated output and I ended up using a modification that uses
    as the terminating character. That makes the newline splitting work even for the dos output ending with

    .

    1
    2
    3
    IFS="$(printf '

    ')"

    在我的系统(Ubuntu 17.10)上,无论是从命令行(输入sh中)还是作为sh脚本执行,您的示例都能按需要工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [bash]§ sh
    $ STR="Hello
    World"
    $ echo $STR
    Hello
    World
    $ exit
    [bash]§ echo"STR="Hello
    World"
    > echo \$STR"> test-str.sh
    [bash]§ cat test-str.sh
    STR="Hello
    World"
    echo $STR
    [bash]§ sh test-str.sh
    Hello
    World

    我想这回答了你的问题:它只是起作用。(我还没有试图弄清楚细节,比如在什么时候,在sh中换行符取代
    )。

    但是,我注意到,在使用bash执行时,相同的脚本的行为会有所不同,并会打印出Hello
    World

    1
    2
    3
    [bash]§ bash test-str.sh
    Hello
    World

    我已经设法用bash得到了期望的输出,如下所示:

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

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

    下面还提供了所需的输出:

    1
    2
    [bash]§ echo"Hello
    > World"