在 Bourne Shell 中读取多行

在 Bourne Shell 中读取多行

我正在尝试将两行读入两个变量。在 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 环境将包含两个变量firstlinesecondline(在读取两者之后),但当子 shell 终止时将被销毁,丢弃这两个变量。

这对于 POSIXshbash.

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 shshell。

答案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 lastpipebash 才会将值保留在管道符号的右侧。 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

相关内容