在Perl中,以下代码将从命令行args或stdin:
中指定的文件中读取while (<>) {
print($_);
}
这很方便。我只是想知道什么是从bash中读取文件或stdin的最简单方法。
答案 0 :(得分:341)
如果调用脚本,以下解决方案将从文件中读取
使用文件名作为第一个参数$1
,否则来自标准输入。
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
如果另外定义,则替换${1:-...}
需要$1
使用自己进程的标准输入的文件名。
答案 1 :(得分:93)
也许最简单的解决方案是使用合并重定向运算符重定向stdin:
#!/bin/bash
less <&0
Stdin是文件描述符零。上面将输入管道传输给你的bash脚本到less的stdin。
答案 2 :(得分:63)
这是最简单的方法:
#!/bin/sh
cat -
用法:
$ echo test | sh my_script.sh
test
要将 stdin 分配给变量,您可以使用:STDIN=$(cat -)
或只是STDIN=$(cat)
,因为不需要运算符(根据@mklement0 comment)。< / p>
要解析标准输入中的每一行,请尝试以下脚本:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
要从文件或 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")
注意:
-
read -r
- 不要以任何特殊方式处理反斜杠字符。将每个反斜杠视为输入行的一部分。- 未设置
IFS
,默认情况下,行的开头和结尾的 Space 和 Tab 的序列将被忽略(修剪)- 当行包含单个
printf
,echo
或-e
时,使用-n
代替-E
以避免打印空行。但是,通过使用env POSIXLY_CORRECT=1 echo "$line"
执行支持它的外部 GNUecho
,有一种解决方法。请参阅:How do I echo "-e"?
请参阅:stackoverflow SE上的How to read stdin when no arguments are passed?
答案 3 :(得分:14)
我认为这是直截了当的方式:
$ 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
答案 4 :(得分:13)
只要echo
打破输入流,IFS
解决方案就会添加新行。 @fgm's answer可以稍微修改一下:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
答案 5 :(得分:7)
问题中的Perl循环从命令行上的 all 文件名参数读取,如果没有指定文件则从标准输入读取。如果没有指定文件,我看到的答案似乎都处理单个文件或标准输入。
虽然经常被嘲笑为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中运行,因此循环中设置的变量可以在循环外部访问。
答案 6 :(得分:4)
Perl的行为,OP中给出的代码可以不带或多个参数,如果参数是单个连字符-
,则将其理解为stdin。此外,文件名始终可以$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
答案 7 :(得分:2)
更准确......
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
答案 8 :(得分:2)
请尝试以下代码:
while IFS= read -r line; do
echo "$line"
done < file
答案 9 :(得分:1)
代码${1:-/dev/stdin}
只会理解第一个参数,那么,这个怎么样。
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
答案 10 :(得分:1)
我发现这些答案都不可接受。特别是,接受的答案仅处理第一个命令行参数并忽略其余部分。它试图模拟的Perl程序处理所有命令行参数。所以接受的答案甚至都没有回答这个问题。其他答案使用bash扩展名,添加不必要的“cat&#39;命令,仅适用于将输入回显到输出的简单情况,或者只是不必要的复杂。
然而,我必须给他们一些信任,因为他们给了我一些想法。这是完整的答案:
#!/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
答案 11 :(得分:1)
#!/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
答案 12 :(得分:1)
两种原则方式:
对早期答案的小修改:
使用cat
,而不是less
。它更快,您不需要分页。
使用 $1
读取第一个参数文件(如果存在)或使用 $*
读取所有文件(如果存在)。如果这些变量为空,则从 stdin 读取(就像 cat
那样)
#!/bin/bash
cat $* | ...
写入命名管道有点复杂,但这允许您将标准输入(或文件)视为单个文件:
使用 mkfifo
创建管道。
并行化写入过程。如果未读取命名管道,则可能会阻塞。
要将标准输入重定向到子进程(在这种情况下是必要的),请使用 <&0
(与其他人评论的不同,这里不是可选的)。 >
#!/bin/bash
mkfifo /tmp/myStream
cat $* <&0 > /tmp/myStream & # separate subprocess (!)
AddYourCommandHere /tmp/myStream # process input like a file,
rm /tmp/myStream # cleaning up
仅当没有给出参数时才创建命名管道。这对于从文件中读取可能更稳定,因为命名管道偶尔会阻塞。
#!/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
答案 13 :(得分:0)
以下适用于标准sh
(在Debian上使用dash
测试过)并且非常易读,但这是一个品味问题:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
详细信息:如果第一个参数非空,则cat
该文件,否则为cat
标准输入。然后整个if
语句的输出由commands_and_transformations
处理。
答案 14 :(得分:0)
我结合了以上所有答案,并创建了一个适合我需要的shell函数。这是从我的2台Windows10机器的cygwin终端获得的,它们之间有一个共享文件夹。我需要能够处理以下问题:
cat file.cpp | tx
tx < file.cpp
tx file.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
}
如果您有任何办法可以进一步优化此功能,我想知道。
答案 15 :(得分:0)
这个在终端上很容易使用:
$ echo '1\n2\n3\n' | while read -r; do echo $REPLY; done
1
2
3
答案 16 :(得分:0)
作为解决方法,您可以在/ dev目录中使用stdin设备
....| for item in `cat /dev/stdin` ; do echo $item ;done
答案 17 :(得分:0)
白...
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
我得到以下输出:
Ignored 1265 characters from standard input. Use "-stdin" or "-" to tell how to handle piped input.
然后决定为:
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
答案 18 :(得分:-1)
怎么样
for line in `cat`; do
something($line);
done