【问题标题】:How to avoid bash command substitution to remove the newline character?如何避免 bash 命令替换以删除换行符?
【发布时间】:2013-02-17 12:37:14
【问题描述】:

为了加快某些 bash 脚本的执行速度,我想使用命令替换将命令的结果保存在变量中,但是命令替换用空格替换了 0x0A 换行符。例如:

a=`df -H`

a=$( df -H )

当我想进一步处理$a 时,换行符被一个空格替换,所有行现在都在一行上,这更难 grep:

echo $a

避免换行符被命令替换删除的简单技巧是什么?

【问题讨论】:

  • 我发现使用eval时也会出现这个问题。

标签: bash newline command-substitution


【解决方案1】:

不删除非尾随换行符

您正在寻找的换行符在那里,您只是看不到它们,因为您使用 echo 而不引用变量。

验证

$ a=$( df -H )
$ echo $a
Filesystem Size Used Avail Use% Mounted on /dev/sda3 276G 50G 213G 19% / udev 2.1G 4.1k 2.1G 1% /dev tmpfs 832M 820k 832M 1% /run none 5.3M 0 5.3M 0% /run/lock none 2.1G 320k 2.1G 1% /run/shm
$ echo "$a"
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3       276G   50G  213G  19% /
udev            2.1G  4.1k  2.1G   1% /dev
tmpfs           832M  820k  832M   1% /run
none            5.3M     0  5.3M   0% /run/lock
none            2.1G  320k  2.1G   1% /run/shm
$ 

尾随换行符被删除

正如 @user4815162342 正确指出的那样,虽然输出中的换行符没有被删除,但 尾随换行符 会通过命令替换被删除。请参阅下面的实验:

$ a=$'test\n\n'
$ echo "$a"
test


$ b=$(echo "$a")
$ echo "$b"
test
$

在大多数情况下,这无关紧要,因为echo 将添加已删除的换行符(除非使用-n 选项调用它),但在某些极端情况下,输出中有多个尾随换行符一个程序,并且由于某种原因它们很重要。

解决方法

1。添加虚拟字符

在这些情况下,如 @Scrutinizer 所述,您可以使用以下解决方法:

$ a=$(printf 'test\n\n'; printf x); a=${a%x}
$ echo "$a"
test


$ 

解释:字符x被添加到输出中(使用printf x),在换行符之后。由于换行符不再尾随,因此它们不会被命令替换删除。下一步是删除我们添加的x,使用${a%x} 中的% 运算符。现在我们有了原始输出,所有换行符都存在!!!

2。使用进程替换读取

我们可以代替使用命令替换将程序的输出分配给变量,而是使用process substitution 将程序的输出提供给read 内置命令(感谢@ormaaj)。进程替换保留所有换行符。将输出读取到变量有点棘手,但您可以这样做:

$ IFS= read -rd '' var < <( printf 'test\n\n' ) 
$ echo "$var"
test


$ 

解释:

  • 我们将读取命令的internal field separator 设置为空,使用IFS=。否则read 不会将整个输出分配给var,而只会分配给第一个标记。
  • 我们使用选项-rd '' 调用readr 用于防止反斜杠充当特殊字符,d '' 将分隔符设置为空,以便 read 读取整个输出,而不仅仅是第一行。

3。从管道中读取

我们可以代替使用命令或进程替换将程序的输出分配给变量,而是将程序的输出通过管道传递给read 命令(归功于@ormaaj)。管道还保留所有换行符。但是请注意,这次我们使用the shopt builtin 设置lastpipe shell 可选行为。这是必需的,以便在当前 shell 环境中执行 read 命令。否则,该变量将在子 shell 中分配,并且无法从脚本的其余部分访问。

$ cat test.sh 
#!/bin/bash
shopt -s lastpipe
printf "test\n\n" | IFS= read -rd '' var
echo "$var"
$ ./test.sh 
test


$

【讨论】:

  • 尾随 换行符仍然被删除,但恐怕无法避免。 (OP 可能不关心这些,但总的来说这很好。)
  • @user4815162342:有趣的是,使用a=$(printf 'test\n\n'; printf x); echo "${a%x}"
  • 这不是一个很好的解决方法。没有一种好方法可以避免使用命令替换来去除尾随换行符。最好的解决方案是使用read 和非标准选项来进行分配。 shopt -s lastpipe; printf %s "$myData" | IFS= read -rd '' var。 Bash 的printf -v 在一些类似的情况下也很有用。
  • @user000001 在 Bash 中,在重定向中使用进程替换而不是依赖 lastpipe(尽管我通常使用 lastpipe)更为常见且向后兼容。您注意到的原因是由于工作控制。交互式 shell 具有set -m,它强制管道在单独的进程组中运行,这需要最后一个元素的子 shell。由于作业控制不适用于子外壳的子外壳,因此将所有内容包装在 (...) 中是一种解决方法(无论如何,我在交互测试时几乎总是这样做)。
  • 注意:read 在遇到 EOF 时以非零值退出。这会导致方法 2 和 3 以非零值退出,因此屏蔽了主命令的退出状态。在方法 1 中使用main_cmd ; printf x 也会覆盖主命令退出代码并干扰错误处理。尝试使用 main_cmd &amp;&amp; printf x 代替(如果尾随行仅在成功退出时很重要)或 main_cmd ; ec=$?; printf x; exit $ec 在成功和错误情况下保留尾随空格。
【解决方案2】:

我试图解决这个问题,因为我正在使用 bash 流式传输在 F# 脚本上运行解释器的结果。经过一些试验和错误,这最终解决了问题:

$ cat fsi.ch
#!/bin/bash
echo "$(fsharpi --quiet --exec --nologo $1)"

$ fsi.ch messages.fsx
Welcome to my program. Choose from the menu:
new | show | remove

当然,假设您需要运行一个终端程序。 希望这会有所帮助。

【讨论】:

    【解决方案3】:

    另一个“巧妙的技巧”是使用回车符,它可以防止换行符被剥离,但不会在输出中添加任何内容:

    $ my_func_1 () {
    >     echo "This newline is squashed"
    > }
    $ my_func_2 () {
    >     echo "This newline is not squashed"
    >     echo -n $'\r'
    > }
    $ echo -n "$(my_func_1)" && echo -n "$(my_func_2)" && echo done
    This newline is squashedThis newline is not squashed
    done
    $
    

    但买家要注意:正如 cmets 中提到的,这可以很好地用于仅发送到终端的输出,但是如果您将其传递给另一个进程,您可能会混淆它,因为它可能不会期待奇怪的终止'\r'

    【讨论】:

    • 如果目的是仅以可视方式而不是以编程方式处理输出,这是一个不错的技巧。但是如果您查看echo -n "$(my_func_2)" | cat -A 的输出,您会看到$'\r' 保留在输出中,因此如果接收器进程不期望它可能会导致问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-24
    • 1970-01-01
    相关资源
    最近更新 更多