带有额外空间的多行字符串(保留缩进)

ciz*_*ixs 353 string bash shell echo

我想使用以下内容将一些预定义的文本写入文件:

text="this is line one\n
this is line two\n
this is line three"

echo -e $text > filename
Run Code Online (Sandbox Code Playgroud)

我期待这样的事情:

this is line one
this is line two
this is line three
Run Code Online (Sandbox Code Playgroud)

但得到了这个:

this is line one
 this is line two
 this is line three
Run Code Online (Sandbox Code Playgroud)

我很肯定每个人都没有空间\n,但额外的空间是如何产生的?

new*_*kid 573

为此,Heredoc听起来更方便.它用于向命令解释程序发送多个命令,如excat

cat << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage
Run Code Online (Sandbox Code Playgroud)

'<<'后面的字符串表示停止的位置.

要将这些行发送到文件,请使用:

cat > $FILE <<- EOM
Line 1.
Line 2.
EOM
Run Code Online (Sandbox Code Playgroud)

您还可以将这些行存储到变量中:

read -r -d '' VAR << EOM
This is line 1.
This is line 2.
Line 3.
EOM
Run Code Online (Sandbox Code Playgroud)

这会将行存储到名为的变量中<<.

打印时,请记住变量周围的引号,否则您将看不到换行符.

echo "$VAR"
Run Code Online (Sandbox Code Playgroud)

更好的是,您可以使用缩进使其在代码中更突出.这次只需在"<<"之后添加一个" - "即可阻止标签显示.

read -r -d '' VAR <<- EOM
    This is line 1.
    This is line 2.
    Line 3.
EOM
Run Code Online (Sandbox Code Playgroud)

但是,您必须在代码中使用制表符而不是空格来缩进.

  • 为什么在第2和第5代码块的第一行有<< - 而不是<<?做 - 有什么特别的吗? (32认同)
  • @ sup3rman' - '忽略前导标签.请参阅http://stackoverflow.com/questions/2500436/how-does-cat-eof-work-in-bash (29认同)
  • 使用`read -r -d'中的`-d`选项'VARIABLE << - EOM`对我来说不起作用. (9认同)
  • 如何以状态0进行读取退出?它似乎无法找到行尾(因为它是-d'')并退出1,这在脚本中没有帮助. (5认同)
  • @MishaSlyusarev使用-d'\ 0'并在最后一行的末尾添加\ 0 (5认同)
  • 使用 bash 的 `set -e` 时要小心:在这种情况下 read 返回 1(错误)(没有找到 `''`),所以最好使用 `read -r -d '' VAR &lt;&lt; EOM || :`。另请参阅https://unix.stackexchange.com/a/265164/288001 (5认同)
  • 当我使用 `cat &gt; $FILE &lt;&lt;- EOM...EOM` 时,我收到错误消息 `ambiguous redirect`。有人知道为什么吗? (2认同)

And*_*ner 143

如果您正在尝试将字符串转换为变量,另一种简单的方法是这样的:

USAGE=$(cat <<-END
    This is line one.
    This is line two.
    This is line three.
END
)
Run Code Online (Sandbox Code Playgroud)

如果使用制表符缩进字符串(即'\ t'),则缩进将被删除.如果缩进空格,缩进将保留.

注意:最后一个右括号在另一行上重要.该END文本必须出现在单独一行.

  • 使用此功能时,请务必注意 `echo $USAGE` 与 `echo "$USAGE"` 之间的区别。 (8认同)
  • @deej 来自 [POSIX 规范](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04):_此处文档 ... 继续,直到有一行仅包含分隔符和 &lt;换行&gt;_ (3认同)
  • 这很重要吗?在我的 mac 上使用 `)` 在同一行上工作。我认为这是因为 `$(` 和 `)` 之间的内容将在其自己的宇宙中执行,而实际的命令无论如何都看不到 ')'。我想知道它是否也适用于其他人。 (2认同)
  • @Fornost 这与我所说的并不矛盾 (2认同)

Jos*_*lly 73

echo在传递给它的参数之间添加空格.$text受变量扩展和单词拆分的影响,因此您的echo命令相当于:

echo -e "this" "is" "line" "one\n" "this" "is" "line" "two\n"  ...
Run Code Online (Sandbox Code Playgroud)

你可以看到在"this"之前会添加一个空格.您可以删除换行符,并引用$text以保留换行符:

text="this is line one
this is line two
this is line three"

echo "$text" > filename
Run Code Online (Sandbox Code Playgroud)

或者您可以使用printf,它比echo以下更强大和便携:

printf "%s\n" "this is line one" "this is line two" "this is line three" > filename
Run Code Online (Sandbox Code Playgroud)

bash,支持大括号扩展,你甚至可以做:

printf "%s\n" "this is line "{one,two,three} > filename
Run Code Online (Sandbox Code Playgroud)

  • 感谢您解释为什么 mod 在使用“echo”命令时会看到额外的空间。这个答案在 bash 脚本中使用时也很有效(与交互式 shell 会话相反)。感觉这是真正的答案,应该是公认的答案。 (2认同)
  • 这应该被接受的答案。所有其他答案都提供了大量有趣的*其他*方法来解决 OP 的痛苦,但从技术上讲,问题是“额外的空间是如何产生的”并且没有人解决这个问题:-) !!! 谢谢你,乔希让 -1 变得神秘! (2认同)

Chr*_*aes 37

在bash脚本中,以下工作:

#!/bin/sh

text="this is line one\nthis is line two\nthis is line three"
echo -e $text > filename
Run Code Online (Sandbox Code Playgroud)

或者:

text="this is line one
this is line two
this is line three"
echo "$text" > filename
Run Code Online (Sandbox Code Playgroud)

cat filename给出:

this is line one
this is line two
this is line three
Run Code Online (Sandbox Code Playgroud)

  • @Macindows我似乎忘记了“echo”的“-e”选项。请再试一次。 (4认同)

Mat*_*ski 34

我找到了更多解决方案,因为我想让每一行都正确缩进:

  1. 你可以使用echo:

    echo    "this is line one"   \
        "\n""this is line two"   \
        "\n""this is line three" \
        > filename
    
    Run Code Online (Sandbox Code Playgroud)

    如果你"\n"刚刚\放在一行的末尾,它就不起作用了.

  2. 或者,您可以使用printf以获得更好的可移植性(我碰巧遇到了很多问题echo):

    printf '%s\n' \
        "this is line one"   \
        "this is line two"   \
        "this is line three" \
        > filename
    
    Run Code Online (Sandbox Code Playgroud)
  3. 另一个解决方案可能是:

    text=''
    text="${text}this is line one\n"
    text="${text}this is line two\n"
    text="${text}this is line three\n"
    printf "%b" "$text" > filename
    
    Run Code Online (Sandbox Code Playgroud)

    要么

    text=''
    text+="this is line one\n"
    text+="this is line two\n"
    text+="this is line three\n"
    printf "%b" "$text" > filename
    
    Run Code Online (Sandbox Code Playgroud)
  4. 通过混合printf和实现另一种解决方案sed.

    if something
    then
        printf '%s' '
        this is line one
        this is line two
        this is line three
        ' | sed '1d;$d;s/^    //g'
    fi
    
    Run Code Online (Sandbox Code Playgroud)

    在将缩进级别硬编码到代码中时,重构像这样格式化的代码并不容易.

  5. 可以使用辅助函数和一些变量替换技巧:

    unset text
    _() { text="${text}${text+
    }${*}"; }
    # That's an empty line which demonstrates the reasoning behind 
    # the usage of "+" instead of ":+" in the variable substitution 
    # above.
    _ ""
    _ "this is line one"
    _ "this is line two"
    _ "this is line three"
    unset -f _
    printf '%s' "$text"
    
    Run Code Online (Sandbox Code Playgroud)


Gab*_*les 10

难以阅读:

对于我的一般口味来说,这看起来太像 bash 了:)(很难阅读):

cat << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage
Run Code Online (Sandbox Code Playgroud)

更好、更容易阅读:

让我们来一些更 Pythonic 的东西(这仍然是 bash):

text="this is line one
      this is line two
      this is line three\n"
dedent text
printf "$text"             # print to screen
printf "$text" > file.txt  # print to a file
Run Code Online (Sandbox Code Playgroud)

啊……这样好多了。:) 这让我想起了我在这里使用的Pythontextwrap.dedent()函数。

下面是这个神奇dedent函数的样子:

dedent() {
    local -n reference="$1"
    reference="$(echo "$reference" | sed 's/^[[:space:]]*//')"
}
Run Code Online (Sandbox Code Playgroud)

屏幕输出示例:

this is line one
this is line two
this is line three
Run Code Online (Sandbox Code Playgroud)

如果不调用dedent textfirst`,输出将如下所示:

this is line one
      this is line two
      this is line three
Run Code Online (Sandbox Code Playgroud)

变量是通过引用text传递的,这样函数内部的修改就会影响到函数外部的变量。dedent

有关更多详细信息、解释和参考,请参阅我的其他答案:Equivalent of python's textwrap dedent in bash

您最初的尝试有问题

OP的引述(加上我的强调):

我确信每个后面都没有空格\n,但是多余的空格是如何出来的呢?

您最初的尝试是这样的:

text="this is line one\n
this is line two\n
this is line three"
echo -e $text
Run Code Online (Sandbox Code Playgroud)

...但是您的输出在第二行和第三行之前有一个额外的空格。为什么?

通过推论和实验,我的结论是echo将行尾的实际换行符(实际按下时得到的换行符Enter)转换为空格。因此,该空格出现在文本中每个行之后的行之前。\n

\因此,解决方案是通过在字符串引号内的任何行末尾添加反斜杠来转义每行末尾的真正换行符,如下所示:

text="this is line one\n\
this is line two\n\
this is line three"

echo -e "$text"
Run Code Online (Sandbox Code Playgroud)

不要在那些尾部反斜杠之前text="this is line one\n \放置空格(如下所示:),否则该空格将直接返回到您的输出中,并导致您与额外的空格出现同样的问题!

或者,只需将我的技术与dedent上面的函数一起使用,它还具有能够缩进代码的附加功能,使其看起来非常漂亮、美观和可读。


Fra*_*yce 7

我听说正在寻找这个答案,但也想将其通过管道传递给另一个命令。给出的答案是正确的,但如果有人想通过管道传输它,您需要在多行字符串之前通过管道传输它,如下所示

echo | tee /tmp/pipetest << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage
Run Code Online (Sandbox Code Playgroud)

这将允许您拥有多行字符串,但也可以将其放入后续命令的标准输入中。


Fra*_*fer 5

以下是我为变量分配多行字符串的首选方法(我认为它看起来不错)。

read -r -d '' my_variable << \
_______________________________________________________________________________

String1
String2
String3
...
StringN
_______________________________________________________________________________
Run Code Online (Sandbox Code Playgroud)

在两种情况下,下划线的数量相同(此处为80)。