以下 Perl 脚本 (my.pl
) 可以从命令行参数中的文件或 standard input (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
。否则,使用自己进程的标准输入的文件名。
也许最简单的解决方案是使用合并重定向运算符重定向标准输入:
#!/bin/bash
less <&0
标准输入是文件描述符零。以上将通过管道传输到您的 bash 脚本的输入发送到 less's 标准输入。
Read more about file descriptor redirection。
<&0
没有任何好处 - 无论有没有它,您的示例都可以正常工作 - 看起来,您从 bash 脚本中调用的工具默认情况下会看到与脚本本身相同的标准输入(除非脚本使用它第一的)。
这是最简单的方法:
#!/bin/sh
cat -
用法:
$ echo test | sh my_script.sh
test
要将 stdin 分配给变量,您可以使用:STDIN=$(cat -)
或仅使用 STDIN=$(cat)
作为不需要的运算符(根据 @mklement0 comment)。
要解析标准输入中的每一行,请尝试以下脚本:
#!/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,默认情况下,行首和行尾的 Space 和 Tab 序列将被忽略(修剪)。 - 当行由单个 -e、-n 或 -E 组成时,使用 printf 而不是 echo 以避免打印空行。但是,有一个解决方法是使用 env POSIXLY_CORRECT=1 echo "$line" 执行支持它的外部 GNU echo。请参阅:如何回显“-e”?
请参阅:How to read stdin when no arguments are passed? at stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
简化为 FILE=${1:--}
。 (狡辩:最好避免使用全大写的 shell 变量,以避免与 environment 变量发生名称冲突。)
${1:--}
是 POSIX 兼容的,因此它应该可以在所有类似 POSIX 的 shell 中工作。在所有此类 shell 中不起作用的是进程替换 (<(...)
);例如,它可以在 bash、ksh、zsh 中工作,但不能在 dash 中工作。此外,最好将 -r
添加到您的 read
命令中,这样它就不会意外吃掉 \
个字符;前置 IFS=
以保留前导和尾随空格。
echo
而中断:如果一行包含 -e
、-n
或 -E
,则不会显示。要解决此问题,您必须使用 printf
: printf '%s\n' "$line"
。我没有将它包含在我之前的编辑中……当我修复此错误 :(
时,我的编辑经常被回滚。
'%s\n'
,则 --
无用
IFS=
与 read
和 printf
而不是 echo
一起使用。 :)
。
我认为这是直接的方法:
$ 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
不需要。
每当 IFS
中断输入流时,echo
解决方案都会添加新行。 @fgm's answer 可以稍作修改:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
的行为:而 read
确实 可能会被字符拆分为多个标记。包含在 $IFS
中,如果您只指定一个 single 变量名(但默认修剪和前导和尾随空格),它只会返回一个 single 标记。
read
和 $IFS
的行为 - echo
本身添加了没有 -n
标志的新行。 “echo 实用程序将任何指定的操作数写入标准输出,由单个空白 (` ') 字符分隔并后跟换行符 (`\n') 字符。”
echo
添加的尾随 \n
:Perl 的 $_
包括从读取的行中以 \n
结尾的行,而 bash 的 read
没有。 (但是,正如@gniourf_gniourf 在其他地方指出的那样,更可靠的方法是使用 printf '%s\n'
代替 echo
)。
问题中的 Perl 循环从命令行上的所有文件名参数读取,如果没有指定文件,则从标准输入读取。如果没有指定文件,我看到的答案似乎都是处理单个文件或标准输入。
尽管经常被准确地嘲笑为 UUOC(cat
的无用使用),但有时 cat
是完成这项工作的最佳工具,并且可以说这是其中之一:
cat "$@" |
while read -r line
do
echo "$line"
done
唯一的缺点是它创建了一个在子 shell 中运行的管道,因此 while
循环中的变量赋值之类的东西在管道之外是不可访问的。解决此问题的 bash
方法是 Process Substitution:
while read -r line
do
echo "$line"
done < <(cat "$@")
这使 while
循环在主 shell 中运行,因此在循环中设置的变量可以在循环外访问。
>>EOF\n$(cat "$@")\nEOF
。最后,一个小问题:while IFS= read -r line
是 while (<>)
在 Perl 中所做的更好的近似(保留前导和尾随空格 - 尽管 Perl 也保留尾随 \n
)。
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
文件名在 $1
中可用。
如果没有给出参数,我们人为地将 -
设置为第一个位置参数。然后我们循环参数。如果参数不是 -
,我们使用 exec
从文件名重定向标准输入。如果此重定向成功,我们将使用 while
循环进行循环。我使用的是标准的 REPLY
变量,在这种情况下您不需要重置 IFS
。如果你想要另一个名字,你必须像这样重置 IFS
(当然,除非你不想要这个并且知道你在做什么):
while IFS= read -r line; do
printf '%s\n' "$line"
done
更精确地...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
和 -r
添加到 read
命令可确保读取每一行 未修改(包括前导和尾随空格)。
请尝试以下代码:
while IFS= read -r line; do
echo "$line"
done < file
read
没有 IFS=
和 -r
,而可怜的 $line
没有它的健康引用。
read -r
符号。 IMO,POSIX 弄错了;该选项应该启用尾部反斜杠的特殊含义,而不是禁用它 - 这样现有脚本(从 POSIX 存在之前)不会因为省略 -r
而中断。然而,我观察到它是 IEEE 1003.2 1992 的一部分,这是 POSIX shell 和实用程序标准的最早版本,但即使在那时它也被标记为附加,所以这是对久违的机会的抱怨。我从来没有遇到过麻烦,因为我的代码不使用 -r
;我一定很幸运。这点不理我。
-r
应该是标准的。我同意在不使用它会导致麻烦的情况下不太可能。但是,损坏的代码就是损坏的代码。我的编辑首先是由严重错过其引号的可怜的 $line
变量触发的。我在处理 read
时修复了它。我没有修复 echo
,因为那是一种会被回滚的编辑。 :(
。
IFS=
是什么东西?为什么有必要?有一些信息in a comment。
我结合了上述所有答案,并创建了一个适合我需要的 shell 函数。这是来自我的两台 Windows 10 机器的 Cygwin 终端,我在它们之间有一个共享文件夹。我需要能够处理以下问题:
猫文件.cpp |发送
TX <文件.cpp
TX 文件.cpp
在指定特定文件名的地方,我需要在复制过程中使用相同的文件名。在输入数据流通过管道传输的地方,我需要生成一个具有小时分钟和秒的临时文件名。共享的主文件夹具有一周中各天的子文件夹。这是出于组织目的。
看哪,满足我需求的终极脚本:
tx ()
{
if [ $# -eq 0 ]; then
local TMP=/tmp/tx.$(date +'%H%M%S')
while IFS= read -r line; do
echo "$line"
done < /dev/stdin > $TMP
cp $TMP //$OTHER/stargate/$(date +'%a')/
rm -f $TMP
else
[ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
fi
}
如果您有任何方法可以进一步优化这一点,我想知道。
#!/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
两种原则方式:
将参数文件和标准输入通过管道传输到单个流中,然后像标准输入一样处理(流方法)
或将标准输入(和参数文件)重定向到命名管道并像文件一样处理(文件方法)
流式方法
对早期答案的小修改:
使用猫,而不是更少。它更快,您不需要分页。
使用 $1 读取第一个参数文件(如果存在)或 $* 读取所有文件(如果存在)。如果这些变量为空,则从标准输入读取(就像 cat 一样)#!/bin/bash cat $* | ...
文件方法
写入命名管道有点复杂,但这允许您将标准输入(或文件)视为单个文件:
使用 mkfifo 创建管道。
并行化写作过程。如果命名管道没有被读取,它可能会阻塞。
要将标准输入重定向到子进程(在这种情况下是必要的),请使用 <&0 (与其他人评论的不同,这里不是可选的)。 #!/bin/bash mkfifo /tmp/myStream cat $* <&0 > /tmp/myStream & # 分离子进程 (!) AddYourCommandHere /tmp/myStream # 像文件一样处理输入,rm /tmp/myStream # 清理
文件方法:变化
只有在没有给出参数时才创建命名管道。这对于从文件读取可能更稳定,因为命名管道偶尔会阻塞。
#!/bin/bash
FILES=$*
if echo $FILES | egrep -v . >&/dev/null; then # if $FILES is empty
mkfifo /tmp/myStream
cat <&0 > /tmp/myStream &
FILES=/tmp/myStream
fi
AddYourCommandHere $FILES # do something ;)
if [ -e /tmp/myStream ]; then
rm /tmp/myStream
fi
此外,它允许您遍历文件和标准输入,而不是将所有内容连接到单个流中:
for file in $FILES; do
AddYourCommandHere $file
done
代码 ${1:-/dev/stdin}
只会理解第一个参数,因此您可以使用它:
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
从标准输入读入变量或从文件读入变量。
现有答案中的大多数示例使用从标准输入读取的每一行立即回显的循环。这可能不是您真正想要做的。
在许多情况下,您需要编写一个脚本来调用只接受文件参数的命令。但在您的脚本中,您可能还想支持标准输入。在这种情况下,您需要先读取完整的标准输入,然后将其作为文件提供。
让我们看一个例子。下面的脚本打印作为文件或通过标准输入传递的证书(PEM 格式)的证书详细信息。
# print-cert script
content=""
while read line
do
content="$content$line\n"
done < "${1:-/dev/stdin}"
# Remove the last newline appended in the above loop
content=${content%\\n}
# Keytool accepts certificate only via a file, but in our script we fix this.
keytool -printcert -v -file <(echo -e $content)
# Read from file
cert-print mycert.crt
# Owner: CN=....
# Issuer: ....
# ....
# Or read from stdin (by pasting)
cert-print
#..paste the cert here and press enter
# Ctl-D
# Owner: CN=....
# Issuer: ....
# ....
# Or read from stdin by piping to another command (which just prints the cert(s) ). In this case we use openssl to fetch directly from a site and then print its info.
echo "" | openssl s_client -connect www.google.com:443 -prexit 2>/dev/null \
| sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' \
| cert-print
# Owner: CN=....
# Issuer: ....
# ....
这个在终端上很容易使用:
$ echo '1\n2\n3\n' | while read -r; do echo $REPLY; done
1
2
3
以下内容适用于标准 sh
(在 Debian 上使用 Dash 测试)并且可读性很强,但这只是个人喜好问题:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
详细信息:如果第一个参数不为空,则 cat
该文件,否则 cat
标准输入。然后整个 if
语句的输出由 commands_and_transformations
处理。
cat "${1:--}" | any_command
。读取 shell 变量并回显它们可能适用于小文件,但不能很好地扩展。
[ -n "$1" ]
可以简化为 [ "$1" ]
。
我认为这些答案中的任何一个都不能接受。特别是,接受的答案只处理第一个命令行参数而忽略其余的。它试图模拟的 Perl 程序处理所有命令行参数。所以接受的答案甚至没有回答这个问题。
其他答案使用 Bash 扩展,添加不必要的“cat”命令,仅适用于将输入回显到输出的简单情况,或者只是不必要的复杂。
但是,我必须给他们一些功劳,因为他们给了我一些想法。这是完整的答案:
#!/bin/sh
if [ $# = 0 ]
then
DEFAULT_INPUT_FILE=/dev/stdin
else
DEFAULT_INPUT_FILE=
fi
# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
while IFS= read -r LINE
do
# Do whatever you want with LINE here.
echo $LINE
done < "$FILE"
done
作为一种解决方法,您可以使用 /dev 目录中的 stdin
设备:
....| for item in `cat /dev/stdin` ; do echo $item ;done
和...
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
我得到以下输出:
忽略标准输入中的 1265 个字符。使用“-stdin”或“-”来告诉如何处理管道输入。
然后决定:
Lnl=$(cat file.txt | wc -l)
echo "Last line: $Lnl"
nl=1
for num in `seq $nl +1 $Lnl`;
do
echo "Number line: $nl"
line=$(cat file.txt | head -n $nl | tail -n 1)
echo "Read line: $line"
nl=$[$nl+1]
done
利用:
for line in `cat`; do
something($line);
done
cat
的输出将被放入命令行。命令行具有最大大小。这也不会逐行读取,而是逐字读取。
/proc/$$/fd/0
和/dev/stdin
有什么区别吗?我注意到后者似乎更常见,看起来更直接。-r
添加到您的read
命令中,这样它就不会意外吃掉\
字符;使用while IFS= read -r line
保留前导和尾随空格。cat
已经完成了。很多时候,处理可能发生在 Awk 脚本中,而 shellwhile read
循环只会使事情复杂化。显然,在某些情况下,您确实需要在 shell 循环中一次处理文件中的一行,但如果您刚刚在 Google 中找到了这个答案,您应该知道这是一种常见的新手反模式。