User1
User1

Reputation: 41163

Bash: Create a file if it does not exist, otherwise check to see if it is writeable

I have a bash program that will write to an output file. This file may or may not exist, but the script must check permissions and fail early. I can't find an elegant way to make this happen. Here's what I have tried.

set +e
touch $file
set -e

if [ $? -ne 0 ]; then exit;fi

I keep set -e on for this script so it fails if there is ever an error on any line. Is there an easier way to do the above script?

Upvotes: 34

Views: 100798

Answers (5)

sorpigal
sorpigal

Reputation: 26086

Why complicate things?

file=exists_and_writeable

if ! [ -e "$file" ] ; then
    touch "$file"
fi

if ! [ -w "$file" ] ; then
    printf 'cannot write to %s\n' "$file"
    exit 1
fi

Or, more concisely,

{ [ -e "$file" ] || touch "$file"; } && \
    ! [ -w "$file" ] && printf 'cannot write to %s' "$file" && exit 1

Upvotes: 42

PhilR
PhilR

Reputation: 5592

Why must the script fail early? By separating the writable test and the file open() you introduce a race condition. Instead, why not try to open (truncate/append) the file for writing, and deal with the error if it occurs? Something like:

$ echo foo > output.txt
$ if [ $? -ne 0 ]; then die("Couldn't echo foo")

As others mention, the "noclobber" option might be useful if you want to avoid overwriting existing files.

Upvotes: 3

Open the file for writing. In the shell, this is done with an output redirection. You can redirect the shell's standard output by putting the redirection on the exec built-in with no argument.

set -e
exec >shell.out  # exit if shell.out can't be opened
echo "This will appear in shell.out"

Make sure you haven't set the noclobber option (which is useful interactively but often unusable in scripts). Use > if you want to truncate the file if it exists, and >> if you want to append instead.

If you only want to test permissions, you can run : >foo.out to create the file (or truncate it if it exists).

If you only want some commands to write to the file, open it on some other descriptor, then redirect as needed.

set -e
exec 3>foo.out
echo "This will appear on the standard output"
echo >&3 "This will appear in foo.out"
echo "This will appear both on standard output and in foo.out" | tee /dev/fd/3

(/dev/fd is not supported everywhere; it's available at least on Linux, *BSD, Solaris and Cygwin.)

Upvotes: 2

SiegeX
SiegeX

Reputation: 140237

Rather than check $? on a different line, check the return value immediately like this:

touch file || exit

As long as your umask doesn't restrict the write bit from being set, you can just rely on the return value of touch

Upvotes: 29

a'r
a'r

Reputation: 36999

You can use -w to check if a file is writable (search for it in the bash man page).

if [[ ! -w $file ]]; then exit; fi

Upvotes: 4

Related Questions