How do I execute any command editing its file (argument) "in place" using bash?
I have a file temp.txt, that I want to sort with the sort
command in bash.
I want the sorted results to replace the original file.
This doesn't work for example (I get an empty file):
sortx temp.txt > temp.txt
Can this be done in one line without resorting to copying to temporary files?
EDIT: The -o
option is very cool for sort
. I used sort
in my question as an example. I run into the same problem with other commands:
uniq temp.txt > temp.txt.
Is there a better general solution?
sort temp.txt -o temp.txt
A sort
needs to see all input before it can start to output. For this reason, the sort
program can easily offer an option to modify a file in-place:
sort temp.txt -o temp.txt
Specifically, the documentation of GNU sort
says:
Normally, sort reads all input before opening output-file, so you can safely sort a file in place by using commands like
sort -o F F
andcat F | sort -o F
. However,sort
with--merge
(-m
) can open the output file before reading all input, so a command likecat F | sort -m -o F - G
is not safe as sort might start writingF
beforecat
is done reading it.
While the documentation of BSD sort
says:
If [the] output-file is one of the input files, sort copies it to a temporary file before sorting and writing the output to [the] output-file.
Commands such as uniq
can start writing output before they finish reading the input. These commands typically do not support in-place editing (and it would be harder for them to support this feature).
You typically work around this with a temporary file, or if you absolutely want to avoid having an intermediate file, you could use a buffer to store the complete result before writing it out. For example, with perl
:
uniq temp.txt | perl -e 'undef $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'
Here, the perl part reads the complete output from uniq
in variable $_
and then overwrites the original file with this data. You could do the same in the scripting language of your choice, perhaps even in Bash. But note that it will need enough memory to store the entire file, this is not advisable when working with large files.
Here's a more general approach, works with uniq, sort and whatnot.
{ rm file && uniq > file; } < file
Tobu's comment on sponge warrants being an answer in its own right.
To quote from the moreutils homepage:
Probably the most general purpose tool in moreutils so far is sponge(1), which lets you do things like this:
% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd
However, sponge
suffers from the same problem Steve Jessop comments on here. If any of the commands in the pipeline before sponge
fail, then the original file will be written over.
$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found
Uh-oh, my-important-file
is gone.
Here you go, one line:
sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt
Technically there's no copying to a temporary file, and the 'mv' command should be instant.