Josh Petitt
Josh Petitt

Reputation: 9589

How to redirect and replace the input file with the output (don't erase myfile when doing "cat myfile > myfile")

What is the right way to redirect to a file used as input and replace the file instead of erasing the file? I want to do something like:

cat myfile > myfile

where the program (i.e., cat) reads the file and then redirects to the file, so in the example above myfile would be unchanged.

I know that the example above will erase the file, as explained here.

What is the proper syntax so the file will be replaced instead of erased? Is stdout and redirection possible, or should the program handle opening and writing the new data to the file? I would like to let my program send output to stdout and then the user can redirect or pipe or whatever.

BTW, I don't want to >> (concatenate). I want the function on the left to write "new" data to the file -- to replace the file contents.

Perhaps a better way to state it is that I want the left side of the redirection to fully occur before the streaming occurs -- is this possible? Do I have a fundamental misunderstanding of bash?

Upvotes: 1

Views: 1568

Answers (3)

suspectus
suspectus

Reputation: 17288

I'm taking your question literally here. By introducing a pipe to read the output of cat, the second pipe process reads the output from stdin and redirects it to the original file name, resulting in an unchanged file:

cat myfile | cat - > myfile

DANGER: This is a race condition. There is no guarantee that the first process can read the entire comments of myfile before the second process truncates it with the output redirection.

Upvotes: 0

Keith Thompson
Keith Thompson

Reputation: 263507

Presumably you're interested in doing something more complex than cat myfile.

Some commands have command-line arguments that let you do this (typically -i) -- but those options work by writing to a temporary file and then renaming the temporary.

You can do the same thing yourself:

cat myfile > myfile.$$ && mv myfile.$$ myfile

I often use $$, which expands to my shell's process ID, as a suffix for a temporary file; it makes it fairly unlikely that the name will collide with some other file.

Using && means that it won't clobber your input file unless the cat command succeeds.

One possible problem with this approach is that your new file myfile.$$ is a newly created file, so it won't keep the permissions of the original input file. If you have tne GNU Coreutils version of the chmod command, you can avoid that problem:

cat myfile > myfile.$$ && \
    chmod --reference=myfile myfile.$$ && \
    mv myfile.$$ myfile

Upvotes: 2

that other guy
that other guy

Reputation: 123570

You have to either read all the data to memory first, or write to a temporary file and swap it out.

To read it into memory (like vim, ed and other editors do):

contents=$(<myfile)
cat <<< "$contents" > myfile

To create a temporary file (like sed -i, rsync and other updating tools do):

tmpfile=$(mktemp fooXXXXXX)
cat myfile > "$tmpfile" && mv "$tmpfile" myfile

Upvotes: 2

Related Questions