[英]How to read from a file or standard input in Bash
The following Perl script ( my.pl
) can read from either the file in the command line arguments or from standard input (STDIN):以下 Perl 脚本 (
my.pl
) 可以从命令行参数中的文件或标准输入(STDIN) 中读取:
while (<>) {
print($_);
}
perl my.pl
will read from standard input, while perl my.pl a.txt
will read from a.txt
. perl my.pl
将从标准输入读取,而perl my.pl a.txt
将从a.txt
读取。 This is very handy.这非常方便。
Is there an equivalent in Bash? Bash 中是否有等价物?
The following solution reads from a file if the script is called with a file name as the first parameter $1
and otherwise from standard input.如果使用文件名作为第一个参数
$1
调用脚本,则以下解决方案从文件中读取,否则从标准输入中读取。
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
The substitution ${1:-...}
takes $1
if defined.如果已定义,替换
${1:-...}
需要$1
。 Otherwise, the file name of the standard input of the own process is used.否则,使用自己进程的标准输入的文件名。
Perhaps the simplest solution is to redirect standard input with a merging redirect operator:也许最简单的解决方案是使用合并重定向运算符重定向标准输入:
#!/bin/bash
less <&0
Standard input is file descriptor zero.标准输入是文件描述符零。 The above sends the input piped to your bash script into less's standard input.
以上将通过管道传输到 bash 脚本的输入发送到less 的标准输入中。
Read more about file descriptor redirection . 阅读有关文件描述符重定向的更多信息。
Here is the simplest way:这是最简单的方法:
#!/bin/sh
cat -
Usage:用法:
$ echo test | sh my_script.sh
test
To assign stdin to 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.-
read -r
- 不要以任何特殊方式处理反斜杠字符。 Consider each backslash to be part of the input line.将每个反斜杠视为输入行的一部分。
- Without setting
IFS
, by default the sequences of Space and Tab at the beginning and end of the lines are ignored (trimmed).- 如果没有设置
IFS
,默认情况下,行首和行尾的Space和Tab序列将被忽略(修剪)。- Use
printf
instead ofecho
to avoid printing empty lines when the line consists of a single-e
,-n
or-E
.- 当行由单个
-e
、-n
或-E
组成时,使用printf
而不是echo
以避免打印空行。 However there is a workaround by usingenv POSIXLY_CORRECT=1 echo "$line"
which executes your external GNUecho
which supports it.但是,有一种解决方法是使用
env POSIXLY_CORRECT=1 echo "$line"
执行支持它的外部GNUecho
。 See: How do I echo "-e"?请参阅:如何回显“-e”?
See: How to read stdin when no arguments are passed?请参阅:如何在不传递参数时读取标准输入? at stackoverflow SE
在 stackoverflow SE
I think this is the straightforward 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
The echo
solution adds new lines whenever IFS
breaks the input stream.每当
IFS
中断输入流时, echo
解决方案都会添加新行。 @fgm's answer can be modified a bit: @fgm 的答案可以稍微修改一下:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
The Perl loop in the question reads from all the file name arguments on the command line, or from standard input if no files are specified.问题中的 Perl 循环从命令行上的所有文件名参数中读取,如果未指定文件,则从标准输入中读取。 The answers I see all seem to process a single file or standard input if there is no file specified.
如果没有指定文件,我看到的所有答案似乎都在处理单个文件或标准输入。
Although often derided accurately as UUOC (Useless Use of cat
), there are times when cat
is the best tool for the job, and it is arguable that this is one of them:虽然经常被准确地嘲笑为UUOC (
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 while
loop are not accessible outside the pipeline.唯一的缺点是它创建了一个在子 shell 中运行的管道,因此在管道外部无法访问
while
循环中的变量赋值等内容。 The bash
way around that is Process Substitution : bash
方法是Process Substitution :
while read -r line
do
echo "$line"
done < <(cat "$@")
This leaves the while
loop running in the main shell, so variables set in the loop are accessible outside the loop.这使得
while
循环在主 shell 中运行,因此循环中设置的变量可以在循环外访问。
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. Perl 的行为,OP 中给出的代码可以不带参数,也可以带多个参数,如果参数是单个连字符
-
这被理解为标准输入。 Moreover, it's always possible to have the filename with $ARGV
.此外,始终可以将文件名与
$ARGV
。 None of the answers given so far really mimic Perl's behavior in these respects.到目前为止给出的答案都没有真正模仿 Perl 在这些方面的行为。 Here's a pure Bash possibility.
这是一个纯粹的 Bash 可能性。 The trick is to use
exec
appropriately.诀窍是适当地使用
exec
。
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
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
.如果参数不是
-
,我们使用exec
从文件名重定向标准输入。 If this redirection succeeds we loop with a while
loop.如果此重定向成功,我们将使用
while
循环进行循环。 I'm using the standard REPLY
variable, and in this case you don't need to reset IFS
.我使用的是标准的
REPLY
变量,在这种情况下,您不需要重置IFS
。 If you want another name, you must reset IFS
like so (unless, of course, you don't want that and know what you're doing):如果你想要另一个名字,你必须像这样重置
IFS
(当然,除非你不想要那个并且知道你在做什么):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Please try the following code:请尝试以下代码:
while IFS= read -r line; do
echo "$line"
done < file
More accurately...更精确地...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
I combined all of the above answers and created a shell function that would suit my needs.我结合了上述所有答案并创建了一个适合我需要的 shell 函数。 This is from a Cygwin terminal of my two Windows 10 machines where I had a shared folder between them.
这是来自我的两台 Windows 10 机器的 Cygwin 终端,我在它们之间有一个共享文件夹。 I need to be able to handle the following:
我需要能够处理以下问题:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Where a specific filename is specified, I need to used the same filename during copy.在指定特定文件名的地方,我需要在复制过程中使用相同的文件名。 Where input data stream has been piped through, then I need to generate a temporary filename having the hour minute and seconds.
在输入数据流通过管道传输的地方,我需要生成一个包含小时分和秒的临时文件名。 The shared mainfolder has subfolders of the days of the week.
共享的主文件夹具有一周中各天的子文件夹。 This is for organizational purposes.
这是出于组织目的。
Behold, the ultimate script for my needs:看哪,满足我需求的最终脚本:
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
}
If there is any way that you can see to further optimize this, I would like to know.如果有任何方法可以看到进一步优化这一点,我想知道。
#!/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
Running:跑步:
echo var var2 | bash std.sh
Result:结果:
var var2
Running:跑步:
bash std.sh < <(cat /etc/passwd)
Result:结果:
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
The code ${1:-/dev/stdin}
will just understand the first argument, so you can use this:代码
${1:-/dev/stdin}
只会理解第一个参数,因此您可以使用它:
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
I don't find any of these answers acceptable.我认为这些答案中没有任何一个是可以接受的。 In particular, the accepted answer only handles the first command line parameter and ignores the rest.
特别是,接受的答案仅处理第一个命令行参数并忽略其余部分。 The Perl program that it is trying to emulate handles all the command line parameters.
它试图模拟的 Perl 程序处理所有命令行参数。 So the accepted answer doesn't even answer the question.
所以接受的答案甚至没有回答这个问题。
Other answers use Bash extensions, add unnecessary 'cat' commands, only work for the simple case of echoing input to output, or are just unnecessarily complicated.其他答案使用 Bash 扩展,添加不必要的“cat”命令,仅适用于将输入回显到输出的简单情况,或者只是不必要地复杂。
However, I have to give them some credit, because they gave me some ideas.但是,我必须给他们一些荣誉,因为他们给了我一些想法。 Here is the complete answer:
这是完整的答案:
#!/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
Two principle ways:两种原则方式:
Minor revisions to earlier answers:对早期答案的小修改:
Use cat
, not less
.用
cat
,不能less
。 It's faster and you don't need pagination.它更快,您不需要分页。
Use $1
to read from first argument file (if present) or $*
to read from all files (if present).使用
$1
从第一个参数文件(如果存在)或$*
读取所有文件(如果存在)。 If these variables are empty, read from stdin (like cat
does)如果这些变量为空,则从 stdin 读取(就像
cat
一样)
#!/bin/bash cat $* | ...
Writing into a named pipe is a bit more complicated, but this allows you to treat stdin (or files) like a single file:写入命名管道有点复杂,但这允许您将标准输入(或文件)视为单个文件:
Create pipe with mkfifo
.使用
mkfifo
创建管道。
Parallelize the writing process.并行化写作过程。 If the named pipe is not read from, it may block otherwise.
如果未读取命名管道,则可能会阻塞。
For redirecting stdin into a subprocess (as necessary in this case), use <&0
(unlike what others have been commenting, this is not optional here).要将 stdin 重定向到子进程(在这种情况下是必要的),请使用
<&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
Create named pipe only if no arguments are given.仅当没有给出参数时才创建命名管道。 This may be more stable for reading from files as named pipes can occasionally block.
这对于从文件中读取可能更稳定,因为命名管道偶尔会阻塞。
#!/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
Also, it allows you to iterate over files and stdin rather than concatenate all into a single stream:此外,它还允许您遍历文件和标准输入,而不是将所有内容连接到一个流中:
for file in $FILES; do
AddYourCommandHere $file
done
The following works with standard sh
(tested with Dash on Debian) and is quite readable, but that's a matter of taste:以下内容适用于标准
sh
(在 Debian 上用Dash测试)并且非常易读,但这是一个品味问题:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Details: If the first parameter is non-empty then cat
that file, else cat
standard input.详细信息:如果第一个参数非空,则
cat
该文件,否则cat
标准输入。 Then the output of the whole if
statement is processed by the commands_and_transformations
.然后整个
if
语句的输出由commands_and_transformations
处理。
This one is easy to use on the terminal:这个很容易在终端上使用:
$ echo '1\n2\n3\n' | while read -r; do echo $REPLY; done
1
2
3
As a workaround, you can use the stdin
device in the /dev directory:作为解决方法,您可以使用/dev目录中的
stdin
设备:
....| for item in `cat /dev/stdin` ; do echo $item ;done
With...和...
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
I got the following output:我得到以下输出:
Ignored 1265 characters from standard input.
忽略标准输入中的 1265 个字符。 Use "-stdin" or "-" to tell how to handle piped input.
使用“-stdin”或“-”来告诉如何处理管道输入。
Then decided with for:然后决定使用 for:
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
Use:采用:
for line in `cat`; do
something($line);
done
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.