晚上好,
我想使用一些管道命令过滤文件的内容,然后将结果写回同一个文件。我知道,我不能按照我写的方式做到这一点。坚持,稍等 …
这是我的 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
对于其他问题,之前很多问题中都有解释: