如何在 Bash 中读取文件或 STDIN?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6980090/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 20:51:10  来源:igfitidea点击:

How to read from a file or STDIN in Bash?

bashstdin

提问by Dagang

The following Perl script (my.pl) can read from either the file on the command line args or from STDIN:

以下 Perl 脚本 ( my.pl) 可以从命令行 args 上的文件或从 STDIN 中读取:

while (<>) {
   print($_);
}

perl my.plwill read from STDIN, while perl my.pl a.txtwill read from a.txt. This is very convenient.

perl my.pl将从 STDIN 读取,而perl my.pl a.txt将从a.txt. 这是非常方便的。

Wondering is there an equivalent in Bash?

想知道 Bash 中是否有等价物?

回答by Fritz G. Mehner

The following solution reads from a file if the script is called with a file name as the first parameter $1otherwise from standard input.

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

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

The substitution ${1:-...}takes $1if defined otherwise the file name of the standard input of the own process is used.

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

回答by Ryan Ballantyne

Perhaps the simplest solution is to redirect stdin with a merging redirect operator:

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

#!/bin/bash
less <&0

Stdin is file descriptor zero. The above sends the input piped to your bash script into less's stdin.

Stdin 是文件描述符零。以上将通过管道传输到 bash 脚本的输入发送到 less 的标准输入中。

Read more about file descriptor redirection.

阅读有关文件描述符重定向的更多信息

回答by kenorb

Here is the simplest way:

这是最简单的方法:

#!/bin/sh
cat -

Usage:

用法:

$ echo test | sh my_script.sh
test

To assign stdinto the variable, you may use: STDIN=$(cat -)or just simply STDIN=$(cat)as operator is not necessary (as per @mklement0 comment).

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



To parse each line from the standard input, try the following script:

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

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done


To read from the file or stdin(if argument is not present), you can extend it to:

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

#!/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")

Notes:

- read -r- Do not treat a backslash character in any special way. Consider each backslash to be part of the input line.

- Without setting IFS, by default the sequences of Spaceand Tabat the beginning and end of the lines are ignored (trimmed).

- Use printfinstead of echoto avoid printing empty lines when the line consists of a single -e, -nor -E. However there is a workaround by using env POSIXLY_CORRECT=1 echo "$line"which executes your externalGNU echowhich supports it. See: How do I echo "-e"?

笔记:

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

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

-当行由单个,或组成时,使用printf代替echo来避免打印空行。但是,有一种解决方法是使用它来执行支持它的外部GNU 。请参阅:如何回显“-e”?-e-n-Eenv POSIXLY_CORRECT=1 echo "$line"echo

See: How to read stdin when no arguments are passed?at stackoverflow SE

请参阅:如何在不传递参数时读取标准输入?在 stackoverflow SE

回答by Amir Mehler

I think this is the straight-forward way:

我认为这是直接的方法:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

--

——

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

--

——

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5

回答by David Souther

The echosolution adds new lines whenever IFSbreaks the input stream. @fgm's answercan be modified a bit:

echo解决方案在IFS中断输入流时添加新行。@fgm 的答案可以稍微修改一下:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"

回答by Jonathan Leffler

The Perl loop in the question reads from allthe file name arguments on the command line, or from standard input if no files are specified. The answers I see all seem to process a single file or standard input if there is no file specified.

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

Although often derided accurately as UUOC(Useless Use of cat), there are times when catis the best tool for the job, and it is arguable that this is one of them:

尽管经常被准确地嘲笑为UUOC(Useless Use of cat),但有时cat它是完成这项工作的最佳工具,并且可以说这是其中之一:

cat "$@" |
while read -r line
do
    echo "$line"
done

The only downside to this is that it creates a pipeline running in a sub-shell, so things like variable assignments in the whileloop are not accessible outside the pipeline. The bashway around that is Process Substitution:

唯一的缺点是它创建了一个在子 shell 中运行的管道,因此while循环中的变量赋值等内容在管道外无法访问。在bash周围的办法是进程替换

while read -r line
do
    echo "$line"
done < <(cat "$@")

This leaves the whileloop running in the main shell, so variables set in the loop are accessible outside the loop.

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

回答by gniourf_gniourf

Perl's behavior, with the code given in the OP can take none or several arguments, and if an argument is a single hyphen -this is understood as stdin. Moreover, it's always possible to have the filename with $ARGV. None of the answers given so far really mimic Perl's behavior in these respects. Here's a pure Bash possibility. The trick is to use execappropriately.

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

#!/bin/bash

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

Filename's available in $1.

文件名在$1.

If no arguments are given, we artificially set -as the first positional parameter. We then loop on the parameters. If a parameter is not -, we redirect standard input from filename with exec. If this redirection succeeds we loop with a whileloop. I'm using the standard REPLYvariable, and in this case you don't need to reset IFS. If you want another name, you must reset IFSlike so (unless, of course, you don't want that and know what you're doing):

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

while IFS= read -r line; do
    printf '%s\n' "$line"
done

回答by Webthusiast

Please try the following code:

请尝试以下代码:

while IFS= read -r line; do
    echo "$line"
done < file

回答by sorpigal

More accurately...

更精确地...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file

回答by Takahiro Onodera

Code ${1:-/dev/stdin}will just understand first argument, so, how about this.

代码${1:-/dev/stdin}只会理解第一个参数,所以,这个怎么样。

ARGS='$*'
if [ -z "$*" ]; then
  ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
   echo "$line"
done