下面的 Perl 脚本(my.pl
)可以从命令行参数指定的文件或者标准输入(STDIN)读取:
while (<>) {
print($_);
}
perl my.pl
会从标准输入读取,而 perl my.pl a.txt
会从文件 a.txt
中读取。这非常方便。
Bash 中有类似的功能吗?
以下解决方案将从文件中读取数据,如果脚本被调用时带有文件名作为第一个参数$1
,否则将从标准输入读取。
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
如果定义了参数$1
,则替换表达式${1:-...}
将使用该参数。否则,将使用当前进程标准输入的文件名。
<&0
没有任何好处-您的示例将无论是否使用它都可以正常工作-似乎,从bash脚本中调用的工具默认看到与脚本本身相同的标准输入(stdin)(除非脚本先使用它)。 - mklement0less "$@" <&0
。 - KingBob这是最简单的方法:
#!/bin/sh
cat -
使用方法:
$ echo test | sh my_script.sh
test
要将标准输入分配给变量,您可以使用:STDIN=$(cat -)
或者简单地使用 STDIN=$(cat)
,因为该操作符不是必需的(根据@mklement0评论)。
要从标准输入中解析每一行,请尝试以下脚本:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
要从文件或标准输入流(如果没有传入参数)中读取内容,你可以扩展它为:
要从文件或标准输入流(如果没有传入参数)中读取,您可以将其扩展为:
#!/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")
注意:
-
read -r
- 不要将反斜杠字符视为特殊字符。将每个反斜杠视为输入行的一部分。- 如果不设置
IFS
,默认情况下会忽略行首和行尾的空格和制表符序列(被修剪)。- 使用
printf
而不是echo
可避免在行仅由单个-e
、-n
或-E
组成时打印空行。但是,通过使用env POSIXLY_CORRECT=1 echo "$line"
可以绕过此问题,它会执行您的外部GNUecho
,该命令支持这些选项。参见:How do I echo "-e?"
参见:在stackoverflow SE上如何在没有传递参数时读取标准输入?
[ "$1" ] && FILE=$1 || FILE="-"
简化为 FILE=${1:--}
。(小问题:最好避免使用全大写的_shell_变量,以避免与_environment_变量发生名称冲突。) - mklement0${1:--}
是符合 POSIX 标准的,因此它应该在所有类 POSIX 的 shell 中都可以工作。在所有这样的 shell 中无法工作的是进程替换 (<(...)
); 它将在 bash、ksh、zsh 中工作,但在 dash 中不会。另外,最好在你的 read
命令中添加 -r
,这样它就不会意外地吃掉 \
字符;在前面加上 IFS=
以保留前导和尾随空格。 - mklement0echo
命令:如果一行包含-e
、-n
或-E
选项,则不会显示该行。为了解决此问题,您必须使用printf
命令:printf '%s\n' "$line"
。我在之前的编辑中没有包括它...太常见了,当我修复这个错误时,我的编辑经常被撤销 :(
。 - gniourf_gniourf'%s\n'
,那么--
是无用的。 - gniourf_gniourfIFS=
与read
和printf
而不是echo
。 :)
. - gniourf_gniourf我认为这是最直接的方法:
$ 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
read
默认从标准输入读取,因此无需使用 < /dev/stdin
。 - mklement0echo
的解决方案会在IFS
打断输入流时添加新行。可以对@fgm's answer进行一些修改:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
的行为:虽然read
有可能根据$IFS
中包含的字符分割成多个标记,但如果你只指定一个变量名(默认情况下会修剪前导和尾随空格),它只会返回一个标记。 - mklement0read
和$IFS
的行为 - echo
本身会在没有-n
标志的情况下添加新行。 "echo实用程序将任何指定的操作数,由单个空格(')字符分隔,并后跟换行符(
\ n')字符,写入标准输出。" - David Southerecho
的尾随\n
:Perl的$_
包括从读取的行中的行结束符\n
,而bash的read
则不包括。 (但是,正如@gniourf_gniourf在其他地方指出的那样,更健壮的方法是使用printf '%s\n'
代替echo
)。 - mklement0script.sh example
或者 cat example | script.sh
的能力。 - shmupcat
使用),但有时cat
是最好的工具之一,可以说这是其中之一:cat "$@" |
while read -r line
do
echo "$line"
done
while
循环中的变量赋值等内容在管道外部不可访问。解决这个问题的方法是使用进程替换。while read -r line
do
echo "$line"
done < <(cat "$@")
while
循环在主 shell 中运行,因此在循环中设置的变量可以在循环外部访问。>>EOF\n$(cat "$@")\nEOF
。最后,一个小问题:while IFS= read -r line
更接近Perl中while (<>)
的功能(保留前导和尾随空格-虽然Perl也保留尾随\n
)。 - mklement0Perl的行为,对于OP中给出的代码可以不带参数或带多个参数,如果参数是单破折号-
,则被理解为标准输入。此外,始终可以使用$ARGV
来获取文件名。
到目前为止,迄今为止给出的答案都没有真正模拟Perl在这些方面的行为。这里有一个纯Bash的可能性。关键是适当地使用exec
。
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
文件名可以在$1
中找到。
如果没有给出参数,我们会将-
人为地设置为第一个位置参数。然后我们循环处理参数。如果一个参数不是-
,我们就用exec
从文件名重定向标准输入。如果这个重定向成功,我们就用while
循环继续处理。我使用了标准的REPLY
变量,在这种情况下你不需要重置IFS
。如果你想要另一个名称,你必须像这样重置IFS
(当然,如果你不想这样做并且知道自己在做什么,你也可以不这样做):
while IFS= read -r line; do
printf '%s\n' "$line"
done
while IFS= read -r line; do
echo "$line"
done < file
read
没有IFS=
和-r
,以及可怜的$line
没有健康的引号。 - gniourf_gniourfread -r
的表示法。在我看来,POSIX搞错了;该选项应启用尾随反斜杠的特殊含义,而不是禁用它——这样,早于POSIX存在的现有脚本就不会因省略-r
而出错。然而,我注意到它是IEEE 1003.2 1992的一部分,这是POSIX shell和实用程序标准的最早版本之一,但即使那时它也被标记为附加内容,所以这只是对已经消失的机会的抱怨。我从未因我的代码不使用-r
而遇到麻烦;我一定很幸运。请忽略我关于此事的看法。 - Jonathan Leffler-r
应该成为标准。我同意在不使用它会导致问题的情况下,这种情况不太可能发生。尽管如此,破损的代码就是破损的代码。我的编辑最初是由那个可怜的 $line
变量触发的,它严重缺少引号。我在处理它时修复了 read
。我没有修复 echo
,因为这种编辑会被撤销。 :(
. - gniourf_gniourfwhile IFS= read -r line ; do
printf "%s\n" "$line"
done < file
和
-r`可以确保每行都被_未修改_读取(包括前导和尾随空格)。 - mklement0#!/usr/bin/bash
if [ -p /dev/stdin ]; then
#for FILE in "$@" /dev/stdin
for FILE in /dev/stdin
do
while IFS= read -r LINE
do
echo "$@" "$LINE" #print line argument and stdin
done < "$FILE"
done
else
printf "[ -p /dev/stdin ] is false\n"
#dosomething
fi
运行中:
echo var var2 | bash std.sh
结果:
var var2
运行中:
bash std.sh < <(cat /etc/passwd)
结果:
root:x:0:0::/root:/usr/bin/bash
bin:x:1:1::/:/usr/bin/nologin
daemon:x:2:2::/:/usr/bin/nologin
mail:x:8:12::/var/spool/mail:/usr/bin/nologin
/proc/$$/fd/0
和/dev/stdin
时有什么区别吗?我注意到后者似乎更常见且更直接。 - knowahread
命令中添加-r
参数,以避免意外消耗\
字符;使用while IFS= read -r line
保留行首和行尾空格。请注意不要改变原文意思。 - mklement0/bin/sh
- 你使用的是除bash
或sh
之外的其他 shell 吗? - mklement0cat
已经可以实现这一点。很多时候,处理可以在 Awk 脚本中进行,而 shell 的while read
循环只会让事情变得更加复杂。显然,有些情况下你确实需要从文件中逐行读取并处理,但如果你是在谷歌上找到了这个答案,那么你应该意识到这是一个常见的新手反模式。 - tripleee