我正在尝试将两行读入两个变量。在 Bash 中我会使用这样的东西:
cat << EOF > myfile
line1
line2
EOF
cat myfile | {
read firstline
echo $firstline # "line1" in bash and sh
read secondline
}
echo $firstline # "line1" in bash, empty in sh
echo $secondline
然而在 Bourne Shell 中,$firstline
和$secondline
在命令组之外是空的。我怎样才能在 sh 中做到这一点?
答案1
firstline
(当您测试 中的代码时,您很可能已经设置了bash
,它的值最后应该为空)。
运行管道时
cat myfile | { read firstline; read secondline; }
右侧在子外壳中运行。不是因为,{ ...; }
而是因为它是管道的一部分。子 shell 环境将包含两个变量firstline
和secondline
(在读取两者之后),但当子 shell 终止时将被销毁,丢弃这两个变量。
这对于 POSIXsh
和bash
.
在bash
(4.2+) 中,您可以通过设置 shell 选项来解决此问题lastpipe
。从bash
手册:
管道中的每个命令都作为单独的进程(即在子shell 中)执行。有关子 shell 环境的描述,请参阅命令执行环境。如果
lastpipe
使用内置选项启用shopt
(请参阅下面的描述shopt
),则管道的最后一个元素可能由 shell 进程运行。
这可以在脚本中使用,但不能在启用作业控制的交互式 shell 中使用(是作业控制使其不起作用,而不是交互性)。
例子:
$ cat script.sh
cat << EOF > myfile
line1
line2
EOF
cat myfile | { read firstline; read secondline; }
printf 'first=%s\n' "$firstline"
printf 'second=%s\n' "$secondline"
shopt -s lastpipe
cat myfile | { read firstline; read secondline; }
printf 'first=%s\n' "$firstline"
printf 'second=%s\n' "$secondline"
$ bash script.sh
first=
second=
first=line1
second=line2
在您的特定情况下,您还可以在 POSIXsh
和中bash
,完全取消cat
和 管道,而是read
直接使用两个调用重定向到复合命令:
{ read firstline; read secondline; } <myfile
顺便说一句,您的计算机上很可能没有真正的历史 Bourne shell(除非它是 Solaris 11 之前的 Solaris 系统)。我假设你指的是现代 POSIX sh
shell。
答案2
当您使用管道的右侧时,存在一些限制。
我更改了您的代码以确保变量在使用前未设置,并将其压缩了一点。
#! /bin/sh
unset firstline secondline
printf '%s\n%s\n' line1 line2 > myfile
cat myfile | { read firstline; read secondline
echo "internal 1: $firstline" # "line1" in most shells
echo "internal 2: $secondline" # "line2" in most shells
}
echo "Outside 1: $firstline" # "line1" in ksh, zsh (even called as sh).
# Empty in sh, bash, mksh, dash, ash, yash.
echo "Outside 2: $secondline" # "line2" in ksh, zsh.
# Same as above for other shells.
复合命令的内部{...}
有值,外部 ( Outside
) 在许多情况下是空的(尤其是 POSIX,便携式情况)。
仅当您设置时,shopt -s lastpipe
bash 才会将值保留在管道符号的右侧。 ksh(93) 和 zsh 都保留默认读取的值。
所以,问题是要避免使用管道|
符号,好吧,在这种情况下它非常简单。或者<file { ... }
或{...} <file
并且您将设置为可移植地保留变量的值。
#! /bin/sh
unset firstline secondline
printf '%s\n%s\n' line1 line2 > myfile
{ read firstline; read secondline
printf '%s\n' "internal 1: $firstline" # "line1" in most shells
printf '%s\n' "internal 2: $secondline" # "line2" in most shells
} < myfile
printf '%s\n' "Outside 1: $firstline" # "line1" in most shells.
printf '%s\n' "Outside 2: $secondline" # "line2" in most shells.
除了旧的 Bourne shell 之外,所有 shell 中的该工作集都经过测试(它缺少在当前 shell 中运行的 {...} 结构,解决方法变得更加困难,不值得花时间,请阅读 @schily 答案)。
答案3
read
您不能在中使用此操作,Bourne Shell
因为 Bourne Shell 有两个限制:
管道的右侧始终在子 shell 中运行
使用 时
echo foo | read VAR
,该read
命令始终在子 shell 中运行Bourne Shell 将带有 shell 内置命令的命令列表放入
{ ...; }
子 shell 中。所以{ read A; read B; } < file
也在子 shell 中运行。
如果您想验证这一点,我建议您使用 Solaris Bourne Shell 的高度可移植版本obosh
进行检查。schilytools
看:http://sourceforge.net/projects/schilytools/files/
现代 shell(从ksh93
运行主 shell 进程内管道最右边的程序开始,以防这是内置 shell。因此,当您使用最新版本的 Bourne Shell ( bosh
) 时,这对您有用。bosh
也不会创建子 shell出于{ ...; }
性能原因,这会产生副作用
{ read A; read B; } < file
与bosh
以下内容应该适用于所有 shell:
exec 3<&0 # save standard input as fd 3
exec < file # use file as stdin
read A
read B
exec 0<&3 # restore standard input
exec 3<&- # close file descriptor 3