如何读取Bash中的文件或stdin?

Dag*_*ang 222 bash stdin

在Perl中,以下代码将从命令行args或stdin中指定的文件中读取:

while (<>) {
   print($_);
}
Run Code Online (Sandbox Code Playgroud)

这很方便.我只是想知道在bash中从file或stdin读取的最简单方法是什么.

Fri*_*ner 374

如果使用文件名作为第一个参数调用脚本,则以下解决方案从文件读取,$1否则从标准输入调用.

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"
Run Code Online (Sandbox Code Playgroud)

如果定义了替换${1:-...},$1则使用自己进程的标准输入的文件名.

  • 最好将`-r`添加到`read`命令中,这样它就不会意外地吃掉`\`chars; 使用`IFS = read -r line`来保留前导和尾随空格. (16认同)
  • 您在命令行上提供的文件名可能有空格. (13认同)
  • 使用`/ proc/$$/fd/0`和`/ dev/stdin`有什么区别吗?我注意到后者似乎更常见,看起来更直接. (3认同)
  • 在绝大多数情况下,您应该避免这种情况。如果您只想将输入回显到输出,`cat` 已经做到了。很多情况下,处理可以在 awk 脚本中进行,而 shell 的“while read”循环只会使问题复杂化。显然,在某些情况下,您确实需要在 shell 循环中一次处理一个文件中的一行,但是如果您刚刚在 Google 中找到了这个答案,您应该意识到这是一个常见的新手反模式。 (2认同)

Rya*_*yne 103

也许最简单的解决方案是使用合并重定向运算符重定向stdin:

#!/bin/bash
less <&0
Run Code Online (Sandbox Code Playgroud)

Stdin是文件描述符零.上面将输入管道传输给你的bash脚本到less的stdin.

了解更多关于文件描述符重定向.

  • 在这种情况下使用`<&0`没有任何好处 - 你的例子在有或没有它的情况下也会一样 - 看起来,你在bash脚本中调用的工具默认看到与脚本本身相同的stdin(除非脚本消耗)它首先). (12认同)
  • 这个答案中的"或来自文件"部分在哪里? (4认同)

ken*_*orb 74

这是最简单的方法:

#!/bin/sh
cat -
Run Code Online (Sandbox Code Playgroud)

用法:

$ echo test | sh my_script.sh
test
Run Code Online (Sandbox Code Playgroud)

要将stdin分配给变量,您可以使用:STDIN=$(cat -)或者只是STDIN=$(cat)因为不需要运算符(根据@ mklement0注释).


要从标准输入中解析每一行,请尝试以下脚本:

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done
Run Code Online (Sandbox Code Playgroud)

要从文件或stdin中读取(如果参数不存在),可以将其扩展为:

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
Run Code Online (Sandbox Code Playgroud)

笔记:

- read -r- 不要以任何特殊方式处理反斜杠字符.将每个反斜杠视为输入行的一部分.

-如果不设置IFS,默认情况下的序列SpaceTab在线条的开始和结束都被忽略(修剪).

-使用printf的,而不是echo以避免印刷空行当线由单一的-e,-n或者-E.但是有一种解决方法,通过使用env POSIXLY_CORRECT=1 echo "$line"它来执行支持它的外部 GNU echo.请参阅:如何回显"-e"?

请参阅:如何在没有传递参数时读取stdin?在stackoverflow SE

  • 实际上你的代码仍然因为`echo`而中断:如果一行包含`-e`,`-n`或`-E`,它将不会被显示.要解决这个问题,你必须使用`printf`:`printf'%s \n'"$ line"`.我没有将它包含在我之前的编辑中...当我修复此错误时,我的编辑经常被回滚`:(`. (4认同)
  • 你的回答对我来说很好(我的意思是没有我知道的错误或不需要的功能)——尽管它不像 Perl 那样处理多个参数。事实上,如果你想处理多个参数,你最终会写出 Jonathan Leffler 的优秀答案——事实上你的答案会更好,因为你会使用带有 `read` 和 `printf` 的 `IFS=` 而不是 `echo` . `:)`。 (2认同)

Ami*_*ler 18

我认为这是直截了当的方式:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin
Run Code Online (Sandbox Code Playgroud)

-

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done
Run Code Online (Sandbox Code Playgroud)

-

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5
Run Code Online (Sandbox Code Playgroud)

  • 这不适合海报从stdin或文件参数读取的要求,这只是从stdin读取. (4认同)
  • 抛开@nash的有效反对意见:`read`从stdin _by default_读取,所以`_/dev/stdin`的_no需要_. (2认同)

Dav*_*her 13

echo每当IFS打破输入流时,解决方案都会添加新行.@fgm的答案可以修改一下:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
Run Code Online (Sandbox Code Playgroud)


Jon*_*ler 8

问题中的Perl循环从命令行上的所有文件名参数读取,如果没有指定文件,则从标准输入读取.如果没有指定文件,我看到的答案似乎都处理单个文件或标准输入.

虽然经常被嘲笑为UUOC(无用的使用cat),但有时候cat这是工作的最佳工具,可以说这是其中之一:

cat "$@" |
while read -r line
do
    echo "$line"
done
Run Code Online (Sandbox Code Playgroud)

唯一的缺点是它创建了一个在子shell中运行的管道,因此while在管道外部无法访问循环中的变量赋值.解决这个问题的bash方法是流程替换:

while read -r line
do
    echo "$line"
done < <(cat "$@")
Run Code Online (Sandbox Code Playgroud)

这使得while循环在主shell中运行,因此循环中设置的变量可以在循环外部访问.


gni*_*urf 6

Perl 的行为,OP 中给出的代码可以不带参数,也可以带多个参数,如果参数是单个连字符,则将其-理解为标准输入。此外,文件名总是可以带有$ARGV. 迄今为止给出的答案都没有真正模仿 Perl 在这些方面的行为。这是一个纯粹的 Bash 可能性。诀窍是exec适当地使用。

#!/bin/bash

(($#)) || set -- -
while (($#)); do
   { [[ $1 = - ]] || exec < "$1"; } &&
   while read -r; do
      printf '%s\n' "$REPLY"
   done
   shift
done
Run Code Online (Sandbox Code Playgroud)

文件名在$1.

如果没有给出参数,我们人为地设置-为第一个位置参数。然后我们循环参数。如果参数不是-,我们使用 重定向来自文件名的标准输入exec。如果此重定向成功,我们将循环while循环。我正在使用标准REPLY变量,在这种情况下,您不需要 reset IFS。如果你想要另一个名字,你必须IFS像这样重置(当然,除非你不想要那个并且知道你在做什么):

while IFS= read -r line; do
    printf '%s\n' "$line"
done
Run Code Online (Sandbox Code Playgroud)