处理单个文件作为整个管道的输入和输出

处理单个文件作为整个管道的输入和输出

晚上好,

我想使用一些管道命令过滤文件的内容,然后将结果写回同一个文件。我知道,我不能按照我写的方式做到这一点。坚持,稍等 …

这是我的 bash 脚本。

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"

所以我认为我可以成功地使用进程替换来代替。然后我写道:

grep '^[a-zA-Z.:]' < <(cat "$filepath") | …

这也没有解决任何问题。我希望进程替换能够将我的输入文件内容“保存”到某个地方,例如临时文件中。看来我也没有理解进程替换。

我阅读了有关“就地”版本的线程,但这些文章强调了一些二进制文件的特殊选项,例如sed -i或 ,sort -o但我需要一个通用的解决方案(我的意思是它必须适合任何管道命令)。

首先,为什么“标准管道方式​​”不能做到这一点,下面发生了什么?:/我应该如何解决我的问题?有人可以吗解释我这是怎么回事?

谢谢。

答案1

正如已经提到的,海绵来自更多实用程序是很棒的。我使用此脚本进行模拟以避免 moreutils 依赖:

#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="`mktemp`" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null

你可以像这样使用它:

grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath" 

您无法使用简单的输出重定向来执行此操作,因为重定向发生在命令启动之前,并且输出重定向会截断输出文件。

换句话说,当 grep (管道的第一个简单命令)启动时,最后一个重定向已经截断了输入/输出文件。

据我所知,实际上没有任何标准 UNIX 实用程序可以进行真正的就地编辑。sed -i仅使用临时文件来模拟它。我猜原因是,如果管道步骤失败,真正的就地过滤很容易损坏文件。

至于下面发生的事情——两者|<()使用系统管道,一次通过 IO 一个缓冲区。该机制不会创建临时文件(无论如何都不是真正的(文件系统)文件),并且它会尝试避免一次将整个输入保存在内存中。

答案2

如果你想从同一个文件输入和输出,你可以尝试海绵。正如其描述所述:

sponge reads standard input and writes it out to the specified file. 
Unlike a shell redirect, sponge soaks up all its input before writing 
the output file. This allows constructing pipelines that read from and 
write to the same file.

所以你可以有类似的sed '...' file | grep '...' | sponge [-a] file输入文件并输出到相同的文件


另一方面,使用临时文件也是使用同一文件进行输入和输出的好方法。您可以按如下方式初始化临时文件:

tempfile=`mktemp tempFile.XXXX` # You can replace "tempFile" with any name you want

这会在运行该脚本的目录中创建一个名为“tempFile”的临时文件,扩展名为“XXXX”,其中 x 替换为当前进程号和随机字母的组合(例如 tempFile.AVm7)。

现在您可以修改管道(或任何管道命令),如下所示:

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$tempfile"

过滤后,您可以将临时文件移动到原始文件,如下所示:

mv "$tempfile" "$filepath"

这将消除您的临时文件,并且您仍保留过滤后的原始文件。但是,有时,您最终可能会创建大量可能不需要且尚未销毁的临时文件,因此如果不再需要它们,最好在脚本结束后删除所有临时文件来清理目录。您可以为此编写一个例程,如下所示:

remove_temp_files() {
    rm `find . -name "tempFile.????"`
}

remove_temp_files然后,您只需在脚本末尾调用例程,消除以上述格式创建的所有临时文件。

答案3

使用此处文档命令替换在这种情况下,标准方法是:

grep '^[a-zA-Z.:]' <<IN \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"
$(cat -- "$filepath")
IN

对于其他问题,之前很多问题中都有解释:

相关内容