$(< file ) and $( cat file )

What is the difference between $(< filename ) and $( cat filename ) ? :rolleyes:
Or should i ask ... are they equivalent ?

2 Likes

I can't give you a quantitative answer, but cite man bash :

1 Like

$(< filename ) - shell opens the file. $( cat filename ) - cat opens the file.

Create a simple script:-

#!/bin/ksh -xv

var1=$( cat file )
var2=$( < file )

and run strace to understand the difference:-

strace ./script.ksh 2> strace.out
1 Like

Run strace with -f (follow child processes) to see the full overhead.

1 Like

-f was important to get the real picture!

They are only equivalent in bash, ksh93 and zsh. $(< file) is faster than $(cat file) , but the latter is POSIX compliant whereas the former is not.

Just for fun playing with subsequent args ... consider giving a try to:

# for i in $(seq 1 10) ; do echo $(< /dev/urandom tr -dc '[:alnum:],@#:!?+-' | head -c10) ; done
# for i in $(seq 1 10) ; do echo $(cat /dev/urandom -- tr -dc '[:alnum:],@#:!?+-' | head -c10) ; done
# for i in $(seq 1 10) ; do echo $(cat /dev/urandom | tr -dc '[:alnum:],@#:!?+-' | head -c10) ; done
# for i in $(seq 1 10) ; do echo $(< /dev/urandom | tr -dc '[:alnum:],@#:!?+-'  | head -c10) ; done

It does not work that way. $(<file) is a special case, which is a faster alternative to $(cat file) . If you are using anything other than just a file, it becomes something else. $( ... ) is just command sustitution, so if we leave out that, it becomes:

< /dev/urandom tr -dc '[:alnum:],@#:!?+-' | head -c10

This is the same as tr -dc '[:alnum:],@#:!?+-' < /dev/urandom | head -c10

cat /dev/urandom -- tr -dc '[:alnum:],@#:!?+-' | head -c10

This is strange, it effectively just means cat /dev/urandom | head -c10

cat /dev/urandom | tr -dc '[:alnum:],@#:!?+-' | head -c10

This is the same as the first one with UUOC

< /dev/urandom | tr -dc '[:alnum:],@#:!?+-'  | head -c10

This is equivalent to :< /dev/urandom | tr -dc '[:alnum:],@#:!?+-' | head -c10 , which is that same as :
: | tr -dc '[:alnum:],@#:!?+-' | head -c10

1 Like

It is. In fact, tr -dc etc. are interpreted as input files

cat: tr: No such file or directory
cat: -dc: No such file or directory
cat: '[:alnum:],@#:!?+-': No such file or directory

and generate error messages which are suppressed in above case as /dev/urandom never ends and the pipe is chopped off by the head -c10 from the other side.

1 Like

Yeah, I agree with you regarding the UUOC one.
The test with -- was just bulk try just to see if it would get the -- as an end of options and see how it would handle the rest of the command line.

I ran it on an AIX machine which didn't return an error message, but a fooled output instead, with some strange control character (the kind of output that sometimes may mess up your PuTTY screen ...)

As Yoda and Scruti noticed, I think the confusing point was that, $(< /dev/urandom tr -dc '[:alnum:],@#:!?+-' | head -c10 )
is not interpreted as the special case $(<filename) but as the $( cmd )

This brings me to the question :
Does the special case $(<filename) support only and strictly 1 file ?

Passing more than one file to the redirection wont be interpreted that way, the second file will just be understood as a command name.

$(< file1 file2)

Using more than one redirection lead to undefined behavior.

$(< file1 <file2)
  • ksh ignores all redirections but the first one, i.e. output file1. It doesn't check file2 for readability/existence.

  • bash silently ignores the whole command, i.e. output nothing, however, it returns an error if file1 or file2 isn't readable (or doesn't exists)

1 Like

Obviously $( < filename ) copies the stream with a shell-internal, just like the external command $( cat < filename ) does.
For a concatenation of file1 and file2 you have to use the external command $( cat file1 file2 )
or a string concatenation like this:

s=$( < /etc/passwd )$( < /etc/group )
echo "$s"

Perhaps this is just an aside, but wouldn't redirecting from a named pipe work? That is, if in $(< file1) file1 would be a named pipe being filled by cat file1 file2 ... ?

And second, wouldn't $(< $(cat file1 file2) ) also work? Right now i am travelling with this damn work-laptop and have no U*X-system at hand, so i can only speculate instead of trying....

bakunin