如何在shell(sh,bash)中将多行输入从stdin读入变量并打印其中一行?

79
我想要做的事情如下:
  1. stdin中读取多行输入到变量A
  2. A进行各种操作
  3. 通过管道将A传递到另一个命令,同时不丢失分隔符(\n\r\t等)
当前的问题是,我不能使用read命令来读取它,因为它会在新行处停止读取。
我可以像这样使用cat读取标准输入:
my_var=`cat /dev/stdin`

我知道如何将文本存储在变量中,但是不知道如何打印它。这样换行符、制表符和其他分隔符仍然存在。

我的示例脚本看起来像这样:

#!/usr/local/bin/bash

A=`cat /dev/stdin`

if [ ${#A} -eq 0 ]; then
        exit 0
else
        cat ${A} | /usr/local/sbin/nextcommand
fi
7个回答

85

这对我起作用:

myvar=`cat`

echo "$myvar"

$myvar周围的引号是很重要的。


7
应该这样写:myvar=$(cat); echo "${myvar}"。但除此之外,我的解决方案完全正确。 - gnud
4
gnud,你建议的额外引号字符实际上并不改变功能。 - apenwarr
4
引用非常重要,否则您将依赖IFS使用默认值,这是一个非常不好的做法。 - MestreLion
失败了。如果文件之前没有换行符,echo "$myvar"将向文件中添加一个换行符。 - CommaToast
4
@CommaToast,这是echo命令的功能,而不是引号的功能。请使用“-n”标志(或您平台上的等效标志)。echo只是用于举例说明其用法。 - Tanktalus
显示剩余3条评论

35
在Bash中,有一种替代方法; man bash提到:

命令替换$(cat file)可以被等效但更快的$(< file)所取代。

$ myVar=$(</dev/stdin)
hello
this is test
$ echo "$myVar"
hello
this is test

2
@CommaToast:而且 $(...) 会删除任何尾随的换行符,所以它们完美地契合在一起,不是吗?而且你可以轻松地切换到 printf 来避免这种情况。 - Ingo Karkat
1
不,它们并不完全匹配。在这个解决方案中,无论最初是否有换行符,你最终都会得到一个尾随的换行符。如果你随意切换到printf,现在即使有一个换行符,你也永远不会有一个尾随的换行符了。 - CommaToast
7
公平地说,UNIX 是在 1946 年设计的,本质上只是一台名为打字机的设备升级版,这并不是你的错。我能够想象在 UNIX 的大脑中,回车符来来回回地移动。它就是无法像对待其他字符那样处理换行符。它必须回到打字机般的爬行大脑里。但无论如何,我找到的唯一解决方法是 var=\cat; echo x`,然后在准备输出时输出 ${var%x}`,以保留换行符(或没有换行符)。虽然很丑,但它起作用... - CommaToast
1
@Nikhil Ctrl-C 会中止整个脚本,很难将其转发到子命令。要在读取 N 行后跳过输入,我会使用 head 命令:myVar=$(head -n 10 /dev/stdin) 如果这不适合您,您可能需要自己实现一个 read 循环。 - Ingo Karkat
1
@Nikhil - 在这里终止多行字符串的输入,请使用Ctrl-D而不是Ctrl-C。 - numbsafari
显示剩余6条评论

13

[更新]

如果管道中没有任何内容,此任务将无限期挂起...

var="$(< /dev/stdin)"

我们可以通过对第一个字符进行超时read来防止这种情况。如果它超时了,返回代码将大于128,我们就知道STDIN管道(又名/dev/stdin)为空。

否则,我们通过以下方式获取STDIN的其余部分...

  • 仅为read命令设置IFS为空
  • 使用-r关闭转义
  • 使用-d''消除读取器的定界符。
  • 最后,将其附加到我们最初获得的字符上

因此...

__=""
_stdin=""

read -N1 -t1 __  && {
  (( $? <= 128 ))  && {
    IFS= read -rd '' _stdin
    _stdin="$__$_stdin"
  }
}

这种技术避免使用var="$(command ...)"命令替换,因为这种方法会始终去除任何尾随的换行符。

如果优先选择使用命令替换,为了保留尾随的换行符,我们可以在$()内部附加一个或多个分隔符字符到输出中,然后在外部去除它们。

例如(注意第一个命令中的$(parens)和第二个命令中的${braces})...

_stdin="$(awk '{print}; END {print "|||"}' /dev/stdin)"
_stdin="${_stdin%|||}"

13

tee能够完成任务。

#!/bin/bash
myVar=$(tee)

+1 是因为 tee 很优雅,而且它可以读取。哇,谢谢!但是你没有回答问题,如何打印并改变换行符。 - CommaToast

7

是的,这对我也有效。谢谢。

myvar=`cat`

是相同的意思

myvar=`cat /dev/stdin`

好的。来自 bash 手册的内容:

使用双引号将字符括起来可以保留引号内所有字符的字面值,除了 $、`、\ 和启用历史扩展时的 !。在双引号中,字符 $ 和 ` 仍然保留它们的特殊含义。


7
如果您在输出末尾有保留换行符的要求,请使用以下代码:
myVar=$(cat; echo x)
myVar=${myVar%x}
printf %s "$myVar"

这里使用了来自这里的技巧。


2
不错。最后两行可以替换为 echo -n "${myVar%x}",但是先修复变量更加模块化,我猜。 - antak

3

通过设置选项-d[DELIMITER],也可以使用Read命令。其中 'input' DELIMITER 是一个字符长。如果您设置-read d '',则它将读取到null或所有输入。

只要记住Bash不能在变量中保存空字节。

read -d'' myvar
echo "$myvar"

注意:尽管如此,尾随的换行字节并未被保留。

我在使用-r参数进行读取操作,即:trim() { read -r str; str=${str##+([[:space:]])}; str=${str%%+([[:space:]])}; echo -n "${str}"; },它能够这样工作: $ echo "-$(echo ' a b ' | trim)-"-a b- - Marslo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接