如何在命令中使用文件并将输出重定向到同一文件而不截断?

发布于 2025-01-27 08:01:19 字数 191 浏览 3 评论 0 原文

基本上,我想从文件中以输入文本为输入,从该文件中删除一行,然后将输出发送回同一文件。如果这样可以使它变得更清晰。

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name

但是,当我这样做时,我最终会得到一个空白文件。 有什么想法吗?

Basically I want to take as input text from a file, remove a line from that file, and send the output back to the same file. Something along these lines if that makes it any clearer.

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name

however, when I do this I end up with a blank file.
Any thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

甜`诱少女 2025-02-03 08:01:19

使用 Sponge 用于此类任务。它的一部分是莫比尔。

尝试此命令:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name

Use sponge for this kind of tasks. Its part of moreutils.

Try this command:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name
眼眸里的快感 2025-02-03 08:01:19

您不能这样做,因为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}

这样,请考虑使用 mktemp 创建 tmpfile ,但请注意,它不是POSIX。

You cannot do that because bash processes the redirections first, then executes the command. So by the time grep looks at file_name, it is already empty. You can use a temporary file though.

#!/bin/sh
tmpfile=$(mktemp)
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile}
cat ${tmpfile} > file_name
rm -f ${tmpfile}

like that, consider using mktemp to create the tmpfile but note that it's not POSIX.

无可置疑 2025-02-03 08:01:19

改用SED:

sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name

Use sed instead:

sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name
疏忽 2025-02-03 08:01:19

尝试这个简单的文件,

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

这次您的文件不会空白:),您的输出也印在您的终端。

try this simple one

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

Your file will not be blank this time :) and your output is also printed to your terminal.

眼泪都笑了 2025-02-03 08:01:19

这是非常可能的,您只需要确保在编写输出时,将其写入另一个文件。这可以通过在打开文件描述符后删除文件来完成,但是在写信给该文件描述符后:

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

这仍然是一件风险的事情,因为如果命令无法正常运行,则您' LL丢失文件内容。如果命令返回非零退出代码,可以通过还原文件来缓解这种情况:

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,并且要处理的文件太大,无法在磁盘上进行两次适合,则可以查看此脚本在未分配已经处理过的块的同时,将文件将文件交给指定的命令block块。与往常一样,请阅读使用页面中的警告。

This is very much possible, you just have to make sure that by the time you write the output, you're writing it to a different file. This can be done by removing the file after opening a file descriptor to it, but before writing to it:

exec 3<file ; rm file; COMMAND <&3 >file ;  exec 3>&-

Or line by line, to understand it better :

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

It's still a risky thing to do, because if COMMAND fails to run properly, you'll lose the file contents. That can be mitigated by restoring the file if COMMAND returns a non-zero exit code :

exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-

We can also define a shell function to make it easier to use :

# Usage: replace FILE COMMAND
replace() { exec 3<$1 ; rm $1; ${@:2} <&3 >$1 || cat <&3 >$1 ; exec 3>&- }

Example :

$ echo aaa > test
$ replace test tr a b
$ cat test
bbb

Also, note that this will keep a full copy of the original file (until the third file descriptor is closed). If you're using Linux, and the file you're processing on is too big to fit twice on the disk, you can check out this script that will pipe the file to the specified command block-by-block while unallocating the already processed blocks. As always, read the warnings in the usage page.

找个人就嫁了吧 2025-02-03 08:01:19

您不能将重定向运算符(&gt; &gt;&gt; )用于同一文件,因为它具有更高的优先级,并且会在之前创建/截断文件。该命令甚至被调用。为了避免这种情况,您应该使用适当的工具,例如 Tee Sponge sed -i 或任何其他可以将结果写入文件的工具(例如分类文件-O文件)。

基本上,将输入重定向到同一原始文件没有意义,您应该使用适当的原位编辑器为此,例如Ex Editor(VIM的一部分):

ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name

where:

  • '+CMD'/<代码> -c - 运行任何EX/VIM命令
  • g/datter/d - 使用 global help:g
  • -s - 静音模式( man ex
  • <代码> -C WQ - 执行:写 and :退出命令

您可以使用 SED> SED> SED 实现相同(如已经显示的那样在其他答案中),但是在场 -i )是非标准的freebsd扩展程序(Unix/linux之间的工作方式可能有所不同),并且基本上是 s s tream ed itor,而不是文件编辑器。请参阅: ex模式有任何实际用途吗?

You can't use redirection operator (> or >>) to the same file, because it has a higher precedence and it will create/truncate the file before the command is even invoked. To avoid that, you should use appropriate tools such as tee, sponge, sed -i or any other tool which can write results to the file (e.g. sort file -o file).

Basically redirecting input to the same original file doesn't make sense and you should use appropriate in-place editors for that, for example Ex editor (part of Vim):

ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name

where:

  • '+cmd'/-c - run any Ex/Vim command
  • g/pattern/d - remove lines matching a pattern using global (help :g)
  • -s - silent mode (man ex)
  • -c wq - execute :write and :quit commands

You may use sed to achieve the same (as already shown in other answers), however in-place (-i) is non-standard FreeBSD extension (may work differently between Unix/Linux) and basically it's a stream editor, not a file editor. See: Does Ex mode have any practical use?

七七 2025-02-03 08:01:19

由于这个问题是搜索引擎中的最佳结果,因此这里是基于 https://serverfault.com/a/547331 使用子壳代替 Sponge (通常不是像OS X这样的香草安装的一部分):

echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name

一般情况是:

echo "$(cat file_name)" > file_name

编辑,上述解决方案有一些警告:

  • printf'%s'&lt; string&gt; 应代替 echo echo&lt; string&gt; ,以便包含 -n don'的文件t导致不希望的行为。
  • 命令替代条拖延新线()因此,我们应该将诸如 x 的后缀字符附加到输出,然后通过临时变量的参数扩展喜欢 $ {v%x}
  • 使用临时变量 $ V 踩踏任何现有变量 $ v 的值,因此我们应该在括号中嵌套整个表达式以保留上一个值。
  • 诸如bash之类的外壳的另一个错误/功能是,命令替换条带上了无法打印的字符,例如输出中的 null 。我通过调用 dd if =/dev/Zero bs = 1 count = 1&gt;&gt来验证这一点。 file_name 并使用 cat file_name |在十六进制中查看它| xxd -p 。但是 echo $(cat file_name)| XXD -P 已剥离。 So this answer should not be used on binary files or anything using unprintable characters, as

一般解决方案(Albiet稍慢,内存更大,仍在剥离无法打印的字符)是:

(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)

测试来自 https:// https:// 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

而呼叫 cat file_uniquely_named.txt&gt; 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)上测试过。

我已经从 kos

Since this question is the top result in search engines, here's a one-liner based on https://serverfault.com/a/547331 that uses a subshell instead of sponge (which often isn't part of a vanilla install like OS X):

echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name

The general case is:

echo "$(cat file_name)" > file_name

Edit, the above solution has some caveats:

  • printf '%s' <string> should be used instead of echo <string> so that files containing -n don't cause undesired behavior.
  • Command substitution strips trailing newlines (this is a bug/feature of shells like bash) so we should append a postfix character like x to the output and remove it on the outside via parameter expansion of a temporary variable like ${v%x}.
  • Using a temporary variable $v stomps the value of any existing variable $v in the current shell environment, so we should nest the entire expression in parentheses to preserve the previous value.
  • Another bug/feature of shells like bash is that command substitution strips unprintable characters like null from the output. I verified this by calling dd if=/dev/zero bs=1 count=1 >> file_name and viewing it in hex with cat file_name | xxd -p. But echo $(cat file_name) | xxd -p is stripped. So this answer should not be used on binary files or anything using unprintable characters, as Lynch pointed out.

The general solution (albiet slightly slower, more memory intensive and still stripping unprintable characters) is:

(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)

Test from 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

Should print:

hello
world

Whereas calling cat file_uniquely_named.txt > file_uniquely_named.txt in the current shell:

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

Prints an empty string.

I haven't tested this on large files (probably over 2 or 4 GB).

I have borrowed this answer from Hart Simha and kos.

救星 2025-02-03 08:01:19

一个班轮替代方案 - 将文件的内容设置为可变:

VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name

One liner alternative - set the content of the file as variable:

VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name
千柳 2025-02-03 08:01:19

以下将完成与 Sponge 所做的事情相同的事情,而无需 moreutils

    shuf --output=file --random-source=/dev/zero 

- andural-source =/dev/dev/Zero part Tricks <代码> 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"
}

The following will accomplish the same thing that sponge does, without requiring moreutils:

    shuf --output=file --random-source=/dev/zero 

The --random-source=/dev/zero part tricks shuf into doing its thing without doing any shuffling at all, so it will buffer your input without altering it.

However, it is true that using a temporary file is best, for performance reasons. So, here is a function that I have written that will do that for you in a generalized way:

# 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"
}
趁年轻赶紧闹 2025-02-03 08:01:19

在我面临的大多数情况下,这确实做得很好:

cat <<< "$(do_stuff_with f)" > f

请注意, $(…) strips trailing newlines,&lt;&lt;&lt;&lt; /em>最终的新线,因此总体而言,结果是神奇的令人满意的。
(在 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 Edition:

echo '{ "a": 12 }' > f
cat f
cat <<< "$(jq '.a = 24' f)" > f
cat f

Tures:

{ "a": 12 }
{
  "a": 24
}

This does the trick pretty nicely in most of the cases I faced:

cat <<< "$(do_stuff_with f)" > f

Note that while $(…) strips trailing newlines, <<< ensures a final newline, so generally the result is magically satisfying.
(Look for “Here Strings” in man bash if you want to learn more.)

Full example:

#! /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

This does not truncate the file and yields:

Initial content.
Final content.

Note that I used a function here for the sake of clarity and extensibility, but that’s not a requirement.

A common usecase is JSON edition:

echo '{ "a": 12 }' > f
cat f
cat <<< "$(jq '.a = 24' f)" > f
cat f

This yields:

{ "a": 12 }
{
  "a": 24
}
热鲨 2025-02-03 08:01:19

还有 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

There's also ed (as an alternative to 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
格子衫的從容 2025-02-03 08:01:19

您可以将slurp与posix awk一起使用:

!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
  q = q ? q RS $0 : $0
}
END {
  print q > ARGV[1]
}

You can use slurp with POSIX Awk:

!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
  q = q ? q RS $0 : $0
}
END {
  print q > ARGV[1]
}

Example

无言温柔 2025-02-03 08:01:19

我通常使用 Tee 程序来做到这一点:

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

它可以创建和删除临时性。

I usually use the tee program to do this:

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

It creates and removes a tempfile by itself.

最后的乘客 2025-02-03 08:01:19

尝试一下

echo -e "AAA\nBBB\nCCC" > testfile

cat testfile
AAA
BBB
CCC

echo "$(grep -v 'AAA' testfile)" > testfile
cat testfile
BBB
CCC

Try this

echo -e "AAA\nBBB\nCCC" > testfile

cat testfile
AAA
BBB
CCC

echo "$(grep -v 'AAA' testfile)" > testfile
cat testfile
BBB
CCC
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文