提问人:mike 提问时间:7/15/2011 最后编辑:Chris Stryczynskimike 更新时间:7/20/2022 访问量:70819
如何在命令中使用文件并将输出重定向到同一文件而不截断它?
How can I use a file in a command and redirect output to the same file without truncating it?
问:
基本上,我想从文件中获取输入文本,从该文件中删除一行,然后将输出发送回同一文件。如果这样的话,可以更清楚地了解这些内容。
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name
但是,当我这样做时,我最终会得到一个空白文件。 有什么想法吗?
答:
请改用 sed:
sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name
评论
-i
-i ''
-i
您不能这样做,因为 bash 首先处理重定向,然后执行命令。所以当 grep 查看file_name时,它已经是空的了。不过,您可以使用临时文件。
#!/bin/sh
tmpfile=$(mktemp)
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile}
cat ${tmpfile} > file_name
rm -f ${tmpfile}
像这样,可以考虑使用 来创建 tmpfile,但请注意它不是 POSIX。mktemp
评论
>
grep
sponge
命令的答案。
使用海绵完成此类任务。它是 moreutils 的一部分。
请尝试以下命令:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name
评论
brew install moreutils
sudo apt-get install moreutils
还有(作为替代方案):ed
sed -i
# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' H 'g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' wq | ed -s file_name
一个行备选方案 - 将文件的内容设置为变量:
VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name
评论
$(command substitution)
printf
echo
您可以将 slurp 与 POSIX Awk 一起使用:
!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
q = q ? q RS $0 : $0
}
END {
print q > ARGV[1]
}
评论
您不能对同一文件使用重定向运算符(或),因为它具有更高的优先级,并且它会在调用命令之前创建/截断文件。为避免这种情况,您应该使用适当的工具,例如 、 或任何其他可以将结果写入文件的工具(例如 )。>
>>
tee
sponge
sed -i
sort file -o file
基本上,将输入重定向到相同的原始文件是没有意义的,您应该为此使用适当的就地编辑器,例如 Ex 编辑器(Vim 的一部分):
ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name
哪里:
'+cmd'
/-c
- 运行任何 Ex/Vim 命令g/pattern/d
- 使用全局删除与模式匹配的线 (help :g
)-s
- 静音模式 (man ex
)-c wq
- 执行和命令:write
:quit
你可以用它来实现相同的目的(如其他答案中已经显示的那样),但是就地 () 是非标准的 FreeBSD 扩展(在 Unix/Linux 之间可能有不同的工作方式),基本上它是一个 stream editor,而不是一个文件编辑器。请参阅:防爆模式有什么实际用途吗?sed
-i
试试这个简单的
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
这次您的文件不会为空:)您的输出也会打印到您的终端。
评论
/dev/null
sed
我通常使用 tee 程序来做到这一点:
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name
它自行创建和删除临时文件。
评论
试试这个
echo -e "AAA\nBBB\nCCC" > testfile
cat testfile
AAA
BBB
CCC
echo "$(grep -v 'AAA' testfile)" > testfile
cat testfile
BBB
CCC
评论
由于这个问题是搜索引擎中排名靠前的结果,这里有一个基于 https://serverfault.com/a/547331 的单行,它使用子 shell 而不是(这通常不是像 OS X 这样的普通安装的一部分):sponge
echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name
一般情况是:
echo "$(cat file_name)" > file_name
编辑,上面的解决方案有一些注意事项:
printf '%s' <string>
应该使用,而不是这样包含的文件不会导致意外行为。echo <string>
-n
- 命令替换去掉尾随换行符(这是 bash 等 shell 的错误/功能),因此我们应该在输出中附加一个后缀字符,并通过临时变量(如 )的参数扩展将其删除。
x
${v%x}
- 使用临时变量会踩踏当前 shell 环境中任何现有变量的值,因此我们应该将整个表达式嵌套在括号中以保留以前的值。
$v
$v
- 像 bash 这样的 shell 的另一个错误/功能是命令替换会从输出中去除不可打印的字符。我通过调用并用 .但被剥离了。因此,正如 Lynch 指出的那样,这个答案不应该用于二进制文件或任何使用不可打印字符的东西。
null
dd if=/dev/zero bs=1 count=1 >> file_name
cat file_name | xxd -p
echo $(cat file_name) | xxd -p
一般的解决方案(尽管速度稍慢,内存占用更多,并且仍在剥离不可打印的字符)是:
(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)
从 https://askubuntu.com/a/752451 测试:
printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do (v=$(cat file_uniquely_named.txt; printf x); printf '%s' ${v%x} > file_uniquely_named.txt); done; cat file_uniquely_named.txt; rm file_uniquely_named.txt
应打印:
hello
world
而在当前 shell 中调用:cat file_uniquely_named.txt > file_uniquely_named.txt
printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do cat file_uniquely_named.txt > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt
打印空字符串。
我还没有在大文件(可能超过 2 或 4 GB)上测试过这一点。
我从哈特·西姆哈(Hart Simha)和科斯(kos)那里借用了这个答案。
评论
cat
echo
sponge
perl
cat file_name.txt | grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' | perl -spe'open(STDOUT, ">", $o)' -- -o=file_name.txt
以下内容将完成与此相同的操作,而无需:sponge
moreutils
shuf --output=file --random-source=/dev/zero
该部件在不进行任何洗牌的情况下进行操作,因此它将在不更改输入的情况下缓冲您的输入。--random-source=/dev/zero
shuf
但是,出于性能原因,最好使用临时文件。所以,这是我编写的一个函数,它将以通用的方式为您做到这一点:
# Pipes a file into a command, and pipes the output of that command
# back into the same file, ensuring that the file is not truncated.
# Parameters:
# $1: the file.
# $2: the command. (With $3... being its arguments.)
# See https://stackoverflow.com/a/55655338/773113
siphon()
{
local tmp file rc=0
[ "$#" -ge 2 ] || { echo "Usage: siphon filename [command...]" >&2; return 1; }
file="$1"; shift
tmp=$(mktemp -- "$file.XXXXXX") || return
"$@" <"$file" >"$tmp" || rc=$?
mv -- "$tmp" "$file" || rc=$(( rc | $? ))
return "$rc"
}
评论
$*
真的需要.否则,将变得相同。除此之外,这个答案很棒。"$@"
siphon "two words"
siphon "two" "words"
mktemp
mv
local tmp=$(mktemp "$1.XXXXXX")
local tmp file
tmp=$(mktemp)
mktemp
tmp=$(mktemp) || return
mktemp
local
local
$?
mv -- "$tmp" "$file"
mv
siphon() {
function
function siphon {
function siphon() {
siphon() {
function
这是很有可能的,你只需要确保在你编写输出时,你正在把它写到一个不同的文件中。这可以通过在打开文件描述符后但在写入文件之前删除文件来完成:
exec 3<file ; rm file; COMMAND <&3 >file ; exec 3>&-
或者一行一行,为了更好地理解它:
exec 3<file # open a file descriptor reading 'file'
rm file # remove file (but fd3 will still point to the removed file)
COMMAND <&3 >file # run command, with the removed file as input
exec 3>&- # close the file descriptor
这仍然是一件有风险的事情,因为如果 COMMAND 无法正常运行,您将丢失文件内容。如果 COMMAND 返回非零退出代码,则可以通过恢复文件来缓解这种情况:
exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-
我们还可以定义一个 shell 函数,使其更易于使用:
# Usage: replace FILE COMMAND
replace() { exec 3<$1 ; rm $1; ${@:2} <&3 >$1 || cat <&3 >$1 ; exec 3>&- }
例:
$ echo aaa > test
$ replace test tr a b
$ cat test
bbb
另外,请注意,这将保留原始文件的完整副本(直到第三个文件描述符关闭)。如果您使用的是 Linux,并且您正在处理的文件太大而无法在磁盘上容纳两次,则可以查看此脚本,该脚本将逐个块地将文件通过管道传递到指定的命令,同时取消分配已处理的块。与往常一样,请阅读使用情况页面中的警告。
在我遇到的大多数情况下,这都做得很好:
cat <<< "$(do_stuff_with f)" > f
请注意,虽然去掉了尾随换行符,但确保了最终的换行符,因此通常结果是令人满意的。
(如果您想了解更多信息,请在其中查找“Here Strings”。$(…)
<<<
man bash
完整示例:
#! /usr/bin/env bash
get_new_content() {
sed 's/Initial/Final/g' "${1:?}"
}
echo 'Initial content.' > f
cat f
cat <<< "$(get_new_content f)" > f
cat f
这不会截断文件,并产生:
Initial content.
Final content.
请注意,为了清晰和可扩展性,我在这里使用了一个函数,但这不是必需的。
一个常见的用例是 JSON 版本:
echo '{ "a": 12 }' > f
cat f
cat <<< "$(jq '.a = 24' f)" > f
cat f
这将产生:
{ "a": 12 }
{
"a": 24
}
评论
grep 'moo' file | cat >file