$(<file)
(also works with `<file`
as well as ${<file;}
in ksh93) is a special operator of the Korn shell copied by zsh
and bash
. It does look a lot like command substitution but it's not really.
In POSIX shells, a simple command is:
< file var1=value1 > file2 cmd 2> file3 args 3> file4
All parts are optional, you can have redirections only, command only, assignment only or combinations.
If there are redirections but no command, the redirections are performed (so a > file
would open and truncate file
), but then nothing happens. So
< file
Opens file
for reading, but then nothing happens as there's no command. So the file
is then closed and that's it. If $(< file)
was a simple command substitution, then it would expand to nothing.
In the POSIX specification, in $(script)
, if script
consists only of redirections, that produces unspecified results. That's to allow that special behaviour of the Korn shell.
In ksh (here tested with ksh93u+
), if the script consists of one and only one simple command (though comments are allowed before and after) that consists only of redirections (no command, no assignment) and if the first redirection is a stdin (fd 0) input only (<
, <<
or <<<
) redirection, so:
$(< file)
$(0< file)
$(<&3)
(also $(0>&3)
actually as that's in effect the same operator)
$(< file > foo 2> $(whatever))
but not:
$(> foo < file)
- nor
$(0<> file)
- nor
$(< file; sleep 1)
- nor
$(< file; < file2)
then
- all but the first redirection are ignored (they are parsed away)
- and it expands to the content of the file/heredoc/herestring (or whatever can be read from the file descriptor if using things like
<&3
) minus the trailing newline characters.
as if using $(cat < file)
except that
- the reading is done internally by the shell and not by
cat
- no pipe nor extra process is involved
- as a consequence of the above, since the code inside is not run in a subshell, any modification remain thereafter (as in
$(<${file=foo.txt})
or $(<file$((++n)))
)
- read errors (though not errors while opening files or duplicating file descriptors) are silently ignored.
In zsh
, it's the same except that that special behaviour is only triggered when there's only one file input redirection (<file
or 0< file
, no <&3
, <<<here
, < a < b
...)
However, except when emulating other shells, in < file
that is when there's only one input redirection without commands, outside of command substitution, zsh
runs the $READNULLCMD
(a pager by default), and when there are more redirections or redirections other than < file
(<&3
, <<<text
, <a <b
, >file
, <a >b
...) the $NULLCMD
(cat
by default), so even if $(<&3)
is not recognized as that special operator, it will still work like in ksh
by invoking cat
to do it.
However while ksh
's $(< a < b)
would expand to the contents of a
, in zsh
, it expands to the content of a
and b
(or just b
if the multios
option is disabled), $(< a > b)
would copy a
to b
and expand to nothing, etc.
bash
has a similar operator but with a few differences:
comments are allowed before but not after:
echo "$(
# getting the content of file
< file)"
works but:
echo "$(< file
# getting the content of file
)"
expands to nothing.
like in zsh
, only one file stdin redirection, though there's no fall back to a $READNULLCMD
, so $(<&3)
, $(< a < b)
do perform the redirections but expand to nothing.
for some reason, while bash
does not invoke cat
, it still forks a process that feeds the content of the file through a pipe making it much less of an optimisation than in other shells. It's in effect like a $(cat < file)
where cat
would be a builtin cat
.
as a consequence of the above, any change made within are lost afterwards (in the $(<${file=foo.txt})
, mentioned above for instance, that $file
assignment is lost afterwards).
In bash
, IFS= read -rd '' var < file
(also works in zsh
) is a more effective way to read the content of a text file into a variable. It also has the benefit of preserving the trailing newline characters. See also $mapfile[file]
in zsh
(in the zsh/mapfile
module and only for regular files) which also works with binary files.
Note that the pdksh-based variants of ksh
have a few variations compared to ksh93. Of interest, in mksh
(one of those pdksh-derived shells), in
var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)
is optimised in that the content of the here document (without the trailing newline characters) is expanded without a temporary file or pipe being used as is otherwise the case for here documents, which makes it an effective multi-line quoting syntax.
To be portable to all versions of ksh
, zsh
and bash
, best is to limit to only $(<file)
avoiding comments and bearing in mind that modifications to variables made within may or may not be preserved.
$(<)
is an operator on filenames? Is<
in$(<)
a redirection operator, or not an operator on its own, and must be part of the entire operator$(<)
? – Tim Aug 23 '17 at 21:32$(<file)
is meant to expand to the content offile
in a similar way as$(cat < file)
would. How it's done varies from shell to shell which is described at length in the answer. If you like, you can say that it's a special operator that is triggered when what looks like a command substitution (syntactically) contains what looks like a single stdin redirection (syntactically), but again with caveats and variations depending on the shell as listed here. – Stéphane Chazelas Aug 24 '17 at 08:21n<&m
andn>&m
do the same thing? I didn’t know that, but I guess it’s not too surprising. – Scott - Слава Україні Sep 03 '17 at 05:45dup(m, n)
. I can see some evidence of ksh86 using stdio and somefdopen(fd, "r" or "w")
, so it might have mattered then. But using stdio in a shell makes little sense, so I don't expect you'll find any modern shell where that will make a difference. One difference is that>&n
isdup(n, 1)
(short for1>&n
), while<&n
isdup(n, 0)
(short for0<&n
). – Stéphane Chazelas Sep 03 '17 at 09:08dup2()
;dup()
takes only one argument and, likeopen()
, uses the lowest available file descriptor. (Today I learned that there is adup3()
function.) – Scott - Слава Україні Sep 03 '17 at 15:52<(list)
is portable:$ cat <(echo test)
produces outputtest
. – eel ghEEz Oct 18 '18 at 19:42bash
docs. – x-yuri Nov 30 '23 at 01:48