Bash 重用进程替换文件

Bash 重用进程替换文件

我有一个大脚本,它接受一个文件作为输入并用它做各种事情。这是一个测试版本:

echo "cat: $1"
cat $1
echo "grep: $1"
grep hello $1
echo "sed: $1"
sed 's/hello/world/g' $1

我希望我的脚本能够使用进程替换,但只有第一个命令 ( cat) 有效,而其余命令则无效。我认为这是因为它是一个管道。

$ myscript.sh <(echo hello)

应该打印:

cat: /dev/fd/63
hello
grep: /dev/fd/63
hello
sed: /dev/fd/63
world

这可能吗?

答案1

<(…)构造创建一个管道。管道通过类似 的文件名传递/dev/fd/63,但这是一种特殊类型的文件:打开它实际上意味着复制文件描述符 63。(请参阅结尾这个答案以获得更多解释。)

从管道读取是一种破坏性操作:一旦捕获了一个字节,就无法将其扔回去。因此您的脚本需要保存管道的输出。您可以使用临时文件(如果输入较大则首选)或变量(如果输入较小则首选)。使用临时文件:

tmp=$(mktemp)
cat <"$1" >"$tmp"
cat <"$tmp"
grep hello <"$tmp"
sed 's/hello/world/g' <"$tmp"
rm -f "$tmp"

(您可以将两个调用合并为catas tee <"$1" -- "$tmp"。)使用变量:

tmp=$(cat)
printf "%s\n"
printf "%s\n" "$tmp" | grep hello
printf "%s\n" "$tmp" | sed 's/hello/world/g'

请注意,命令替换$(…)会截断命令输出末尾的所有换行符。为了避免这种情况,请添加一个额外的字符,然后再将其删除。

tmp=$(cat; echo a); tmp=${tmp%a}
printf "%s\n"
printf "%s\n" "$tmp" | grep hello
printf "%s\n" "$tmp" | sed 's/hello/world/g'

顺便一提,不要忘记变量替换周围的双引号。

答案2

当您使用一个文件时,您可以多次读取其数据。当您使用命名管道(实际上是通过进程替换创建的)时,您只能读取它一次。因此grepsed命令接收空输入。

如何理解管道可能是一本很好的读物。)

为了实现进程替换的目的,您可以编写如下内容:

cat $1 | tee >(echo "cat: $1"; cat) | tee >(echo "grep: $1"; grep hello) | (echo "sed: $1"; sed 's/hello/world/g')

但在这种情况下,第二个catgrepsed将并行运行,并且它们的输出交错。这可能更有用:

cat $1 | tee >(cat > cat.txt) | tee >(grep hello > grep.txt) | sed 's/hello/world/g' > sed.txt

答案3

通常的方法是使$1参数可选。然后,可以多次定义FILE=${1-/dev/stdin}和使用。FILE然而,在管道上多次读取将按顺序读取,数据不会重复。

解决此问题的最简单的方法是使用一些临时文件。

if [ -z "$1" ] ; then FILE=$(mktemp); cat >FILE; else FILE=$1; fi

如果您希望显式传递一些文件名(最终/dev/fd/x),可以使用相同的临时文件技巧:

FILE=$(mktemp); cat "$1" >FILE

您还可以复杂地使用tee将输入从 stdin 文件描述符复制到其他几个文件描述符。但最后这个方法会相当繁重。

答案4

通过进程替换获得的 I 文件是不可查找的,具体取决于底层实现,因此您不能多次读取它。

相关内容