Answers:
如果以文件名作为第一个参数调用脚本,则以下解决方案从文件中读取,$1
否则从标准输入中读取。
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
如果定义,则替换${1:-...}
将采用$1
自己过程的标准输入的文件名。
/proc/$$/fd/0
和/dev/stdin
?我注意到后者似乎更常见并且看起来更简单。
-r
到您的read
命令中,以免意外吃到\
字符。用于while IFS= read -r line
保留前导和尾随空格。
/bin/sh
-您是否使用的不是bash
or 的外壳sh
?
也许最简单的解决方案是使用合并的重定向运算符重定向stdin:
#!/bin/bash
less <&0
Stdin是文件描述符零。上面的代码将输入到bash脚本中的输入发送到less的stdin中。
<&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
要从文件或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
。请参阅:如何回显“ -e”?
请参阅:无参数时如何读取stdin?在stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
为FILE=${1:--}
。(Quibble:最好避免使用全大写的Shell变量,以避免名称与环境变量发生冲突。)
${1:--}
是与 POSIX兼容的,因此它应该可以在所有类似POSIX的外壳中使用。在所有这些shell中都无效的是进程替换(<(...)
);例如,它可以在bash,ksh,zsh中使用,但不能在破折号中使用。另外,最好将其添加-r
到您的read
命令中,这样它就不会偶然吃掉\
字符。优先IFS=
保留前导和尾随空格。
echo
:如果线路由-e
,-n
或者-E
,它不会被显示。要解决此问题,您必须使用printf
:printf '%s\n' "$line"
。我没有在以前的编辑中包括它…经常在解决此错误时回滚我的编辑:(
。
--
是无用的,如果第一个参数是'%s\n'
IFS=
with 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
。
echo
只要IFS
中断输入流,该解决方案就会添加新行。@fgm的答案可以修改一下:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
的行为:在read
没有可能分裂成由字符多个令牌。包含在$IFS
,它只返回一个单一的令牌,如果你只指定一个单变量名(但修剪和领先的,默认情况下尾随空白)。
read
并且$IFS
- echo
本身添加了没有-n
标志的新行。“ echo实用程序将任何指定的操作数写入标准输出,这些操作数由单个空格('')字符分隔,后跟换行符(`\ n')字符。
\n
添加以下结尾echo
:Perl $_
包括以\n
读取的行结尾的行,而bash read
则没有。(但是,正如@gniourf_gniourf在其他地方指出的那样,更健壮的方法是使用printf '%s\n'
代替echo
)。
问题中的Perl循环从命令行上的所有文件名参数中读取,或者,如果未指定文件,则从标准输入中读取。如果没有指定文件,我看到的所有答案似乎都在处理单个文件或标准输入。
尽管通常被准确地嘲笑为UUOC(对的无用使用cat
),但有时cat
它是工作的最佳工具,并且可以说这是其中之一:
cat "$@" |
while read -r line
do
echo "$line"
done
唯一的缺点是它创建了在子外壳中运行的管道,因此while
无法在管道外部访问循环中的变量分配之类的内容。在bash
周围的办法是进程替换:
while read -r line
do
echo "$line"
done < <(cat "$@")
这使while
循环在主外壳中运行,因此可以在循环外部访问在循环中设置的变量。
>>EOF\n$(cat "$@")\nEOF
。最后,一个怪癖:while IFS= read -r line
是while (<>)
Perl中的更好近似(保留前导和尾随空白-尽管Perl也保留尾随\n
)。
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
变量,在这种情况下,您不需要reset 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
因为那是可以回滚的编辑类型。:(
。
我找不到这些答案中的任何一个。特别是,接受的答案仅处理第一个命令行参数,而忽略其余参数。试图模拟的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
我结合了以上所有答案,并创建了一个适合我需要的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
}
如果您有任何办法可以进一步优化此功能,我想知道。
以下代码可与标准代码一起使用sh
(已dash
在Debian上进行测试),并且可读性强,但这只是一个趣味问题:
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" ]
。
怎么样
for line in `cat`; do
something($line);
done
cat
将放入命令行。命令行具有最大大小。同样,这不会逐行读取,而是逐字读取。