Search code examples
bashunixconcurrencyflockfuser

Replace file only if not being accessed in bash


My requirement is to replace file only when it is not being accessed. I have following snippet:

if [ -f file ]
then
    while true
    do
       if [ -n "$(fuser "file")" ]
       then
           echo "file is in use..."
       else
           echo "file is free..."
           break
       fi
   done
fi

{
   flock -x 3
   mv newfile file
} 3>file

But I have a doubt that I am not handling concurrency properly. Please give some insights and possible way to achieve this.

Thanks.


Solution

  • My requirement is to replace file only when it is not being accessed.

    Getting requirements right can be hard. In case your actual requirement is the following, you can boil down the whole script to just one command. My guess on the actual requirement (not as strict as the original):

    Replace file without disturbing any programs reading/writing file.

    If this is the case, you can use a very neat behavior: In Unix-like systems file descriptors always point to the file (not path) for which they where opened. You can move or even delete the corresponding path. See also How do the UNIX commands mv and rm work with open files?.

    Example:

    Open a terminal and enter

    i=1; while true; do echo $((i++)); sleep 1; done > file &
    tail -f file
    

    The first command writes output to file and runs in the background. The second command reads the file and continues to print its changing content.

    Open another terminal and move or delete file, for instance with

    mv file file2
    echo overwritten > otherFile
    mv otherFile file2
    rm file2
    echo overwritten > file
    echo overwritten > file2
    

    While executing these commands have a look at the output of tail -f in the first terminal – it won't be affected by any of these commands. You will never see overwritten.

    Solution For New Requirement:

    Because of this behavior you can replace the whole script with just one mv command:

    mv newfile file