2

I want to replace cat:

var=$(cat "filename" 2>/dev/null)

by bashism syntax:

var=$(<"filename")

The problem is that I don't know how to make the bashism silent to avoid such warnings:

bash: filename: No such file or directory

I've tried this:

var=$(2>/dev/null <"filename")

but it does not read existing files into var anymore.

8

Wrapping the assignment into a compound block and using a redirection on that seems to work:

{ var=$(<"$file"); } 2>/dev/null;

e.g.

$ echo hello > test1; rm -f test2
$ file=test1; { var=$(<"$file"); } 2>/dev/null; echo "${var:-[it is empty]}"
hello
$ file=test2; { var=$(<"$file"); } 2>/dev/null; echo "${var:-"[it is empty]"}"
[it is empty]

Just don't use ( .. ) to create a subshell, since then the assigned variable would be lost.

  • 1
    Thanks! I always forget that group command exists beside subshell. – rudimeier Mar 6 '18 at 13:56
3

Test first to make sure it's available and readable:

if [ -f "filename" ] && [ -r "filename" ]; then
    var=$(<"filename")
fi

The -f test tests whether filename refers to a regular file and the -r test tests whether it's readable by you.

  • I don’t think -f is really necessary... This still suffers from TOCTTOU but I doubt there’s anything better, short of using zsh (which handles 2> /dev/null here). – Stephen Kitt Mar 6 '18 at 13:06
  • 1
    @StephenKitt Well, -r will still return true for a directory... – Kusalananda Mar 6 '18 at 13:10
  • 1
    Ah, yes, I can see that causing problems ;-). Perhaps ! -d would be better then; POSIX test -f excludes a few things it can be useful to redirect from (unlike BSD test -f which apparently means “exists and is not a directory”). – Stephen Kitt Mar 6 '18 at 13:12
  • 1
    This would be a race condition, so that there might be still ugly output "sometimes". The files I read are maintained outside of my actual script. I just want to read and handle errors afterwards if any. But I really wonder that this extra "non-cat-bashism" seems to by less flexible than cat. – rudimeier Mar 6 '18 at 13:19
1

Another way is to first check if the file was exist then read it into variable.

[ -e filename ] && var=$(<filename)
1

For reference:

in zsh, you can use the $mapfile special associative array that maps to the content of the file named as the key (note that it's not related to and predates bash's mapfile builtin (which doesn't map files) by a decade or so). It uses mmap() so can only be used on regular files.

zmodload zsh/mapfile
var=$mapfile[filename]

It doesn't output anything if the file cannot be opened or mmaped. To check for errors, you can use the $ERRNO special variable:

ERRNO=0
var=$mapfile[filename]
if ((ERRNO)); then
  : the mapping failed
fi

$ERRNO values can be translated to ENOENT, EACCESS... codes with the $errnos array in the zsh/system module, or to error strings with the syserror builtin in zsh/system as well.

You can also do mapfile[filename]=$var to change the content of the file, it is a true mapping.

Note that while $(<filename) would strip all trailing newline characters from the content of filename, $mapfile[filename] expands to the whole content of the file (including binary files) as-is.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.