4

I have already read: Is there a way to modify a file in-place? I am curious if there is a way to modify a file in place using a command that will not create temporary files. Let's say I create a directory mkdir read_only and then I create some files inside read_only. When I'm happily done creating files, I run chmod 555 read_only. Is it possible to modify any of the files now without the use of a temporary file? Specifically I would like a solution that could be done from a bash script. I do not want to know just if it is possible; I am also seeking a solution. All the ideas I have managed so far with bash/unix commands create temporary files.

Edit: I believe my question is not a duplicate of Can a file be edited with the 'write' permission on it but not on its parent directory? for the following reasons:

  • I am seeking a solution in addition to asking "can". Whereas, they only asked "can".
  • The answers on the possible duplicate do not answer my question fully. I ask for commands that can go in a bash script.
serenesat
  • 1,326

4 Answers4

10

You need the write permission on a directory to create or remove files in it, but not to write to a file in it. Most shell commands, when given an output file, simply open that file for writing, and replace the data that was previously in the file. The > redirection operator truncates the existing file (i.e. deletes the existing file content, resulting in a file with length zero) and starts writing to it. The >> redirection operator causes data to be appended to the end of the file.

Writing to files is limited by the possibilities offered by the low-level interface. You can overwrite bytes in a file in place, append to the end of the file, and truncate a file to a chosen length. You cannot insert bytes while shifting subsequent bytes forward (as in foobarfooNEWSTUFFbar) nor delete bytes while shifting subsequent bytes backwards (as in foobarfor), except by simulating these operations by reading the data to move and writing it at its new location.

The problem with editing files in place is that it's difficult to ensure that the file content remains consistent if something goes wrong (program bug, disk full, power loss, …). This is why robust file processing usually involves writing a temporary file with the new data, then moving that temporary file into place.

Another limitation with editing files in place is that complex operations involving reads and writes are not atomic. This is a problem only if some other task may want to read the file at the same time as your modification. For example, if you change foobar to fooNEWSTUFFbar by reading the last 3 bytes (bar) then writing the new data (foofooNEWSTUFF) and finally appending the old tail (fooNEWSTUFFfooNEWSTUFFbar), a concurrent reader might see fooNEWSTUFF, or even other partial states of the file with only part of the data written. Again, writing to a temporary file first solves this problem, because moving the temporary file in place is an atomic operation.

If you don't care about these limitations, then you can modify a file in place. Appending data is easy (>>), most other transformations are more involved. A common pitfall is that

somefilter <somefile >somefile

does NOT apply somefilter to the file content: the > operator truncates the output file before somefilter starts reading from it.

Joey Hess's moreutils contains a utility called sponge which fixes this problem. Instead of redirecting the output to somefile, you pipe it into sponge, which reads all of its input and then overwrites the existing file with the input that it's read. Note that it's still possible to end up with partial data if the filter fails.

somefilter <somefile | sponge somefile

If you don't have sponge, the portable easy way to fix this is to first read the data into memory:

content=$(cat somefile; echo a)
content=${content%a}

The echo a bit is to preserve newlines at the end of the file — command substitution always strips off trailing newlines. You can then pass the content to a command:

printf %s "$content" | somefilter >somefile

This replaces the content of the file by the output of the filter. If the command fails for any reason, the original content of the file is lost and the file contains whatever data the command wrote before failing.

Beware that this method doesn't work for binary files, because most shells don't support null bytes.

Another way to modify a file in place is to use the ed editor, which bears a strong resemblance to sed, but loads the file into memory and saves it in place, as opposed to sed's line-by-line operation.

Acting on a file without loading it into memory and without creating a temporary file is trickier with only standard shell tools, but it can be done. Shell redirection and most text processing utilities only let you append to a file or overwrite it from the beginning. You can use dd conv=notrunc seek=… to overwrite data at some offset in a file without affecting the parts that aren't being overwritten; see Is there a way to modify a file in-place? for an example.

3

If the directory is read-only but the files within that directory are read/write, then there is nothing stopping you from overwriting those files.

From a script you can write to the files using usual redirection > and >> as well as overwriting them using cp.

What you cannot do is to create a new file in the directory and rename it on top of an existing file. Creating a new file and renaming it is very often desirable due to the rename happening atomically. This provides some protection against data loss and prevents any other process reading the file through the usual name from seeing only a partial file during updates.

The lack of the ability to create a new file and rename on top means you have to think very carefully about what guarantees you need when updating the files.

kasperd
  • 3,580
3

Perl's Tie::File module offers true in-place edit functionality:

perl -MTie::File -e '
    tie @a,"Tie::File","your_file_here";
    # Do something...
'

This makes the elements of @a into the lines of your file and any changes done to @a are reflected in the file even if the file is in a read-only directory.

Joseph R.
  • 39,549
1

No. You can't use sed's or perl's -i switch to edit files in place in a read-only directory. As you correctly assumed, you won't be allowed to create the necessary temporary files:

$ ls -ld read_only/
dr-xr-xr-x 2 terdon terdon 4096 Apr 13 02:16 read_only/
$ ls -l read_only/file 
-rw-r--r-- 1 terdon terdon 3 Apr 13 02:16 read_only/file
$ sed -i 's/a/A/' read_only/file
sed: couldn't open temporary file read_only/sedOEdv8L: Permission denied
$ perl -i -pe 's/a/A/' read_only/file
Can't remove read_only/file: Permission denied, skipping file.

You can, however, use Joseph.R's nifty Perl trick:

$ perl -MTie::File -e 'tie @a,"Tie::File","$ARGV[0]"; $a[0]=~s/a/A/' read_only/file  
terdon
  • 242,166
  • I realized that about sed -i by reading the answers in the first link of my question... Thanks for clarifying Joseph.R's example though! $a[0]=~s/a/A/ is just the kind of operation I was wanting to do! – Kevin Tindall Apr 14 '15 at 13:54