I want to read the whole stdin into variable. I don't want to spawn new processes.

From bash manual:

The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

It mostly works, but this /proc/fd stuff is easily broken by sudo:

# same user. works
[root@okdistr ~]# echo aaa | bash -c 'echo $(</dev/stdin)'

# different user. fail
[root@okdistr ~]# echo aaa | sudo -u nobody bash -c 'echo $(</dev/stdin)'
bash: /dev/stdin: Permission denied

# spawn new process. not want
[root@okdistr ~]# echo aaa | sudo -u nobody bash -c 'echo $(cat)'

# try to read from fd: silently fails
[root@okdistr ~]# echo aaa | sudo -u nobody bash -c 'echo $(<&0)'

# works, but too complex
[root@okdistr ~]# echo aaa | sudo -u nobody bash -c 'a=; while true; do rc=0; read -N1024 b || rc=$?; a=$a$b; [ $rc = 0 ] || break; done; echo "$a"'

[root@okdistr ~]#

Why does $(<&0) fail?

  • Hmmm, looks like there's already an accepted answer. Just saying, /proc/$$/fd/0 would fulfill your purpose. – Abel Cheung Jul 2 '15 at 10:02
  • @AbelCheung No. /dev/stdin is a symlink to /proc/self/fd/0 . Same permission denied – basin Jul 2 '15 at 10:08
  • Oops, sorry that I didn't read carefully about the sudo requirement. – Abel Cheung Jul 2 '15 at 11:06

First, my suggested solution is presented below. After this, each of the two errors that you observed are discussed.

Suggested Solution

Bash variables cannot hold a NUL character. Consequently, it is only possible to read an entire file into a bash variable if the file contains no such characters. Subject to this bash limitation, the following should work for you:

$ echo aaa | { read -rd "" v; echo "$v"; }

With read -d '' var, bash will read stdin until a NUL character is found. Since bash variables cannot contain NUL characters anyway, this approach does not limit you beyond the inherent limitations of bash.

The -r option to read prevents bash from interpreting backslash sequences. Also, if you want to preserve leading and trailing whitespace, then add IFS= before the read statement.

"try to read from fd: silently fails"

# echo aaa | sudo -u nobody bash -c 'echo $(<&0)'

The above silent failure happens even without sudo:

$ echo aaa | bash -c 'echo $(<&0)'

It even happens without creating the subshell:

$ echo aaa | echo $(<&0)

The problem is that &0 is not a valid file name. Observe:

$ echo aaa | cat &0
[1] 22635
bash: 0: command not found

In bash, &0 is only meaningful when combined with < or >.

Let's look again at the bash documentation:

The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

Since cat &0 does not work, one should not expect $(< &0) to work either.

"different user. fail"

This might seem like a reasonable thing to do:

# echo aaa | sudo -u nobody bash -c 'echo $(cat /dev/stdin)'
cat: /dev/stdin: Permission denied

To see why it fails, let's examine the permissions of /dev/stdin:

# echo aaa | sudo -u nobody bash -c 'ls -lH /dev/stdin'
prw------- 1 root root 0 Jul  1 15:42 /dev/stdin

User nobody does not have permission to access that file. This is reasonable: one doesn't want user nobody messing with root's files.

A 'smarter' operating system might know that nobody has access to /dev/stdin in this particular case but, for security purposes, it is probably good that the operating system does not try to out-smart itself.

| improve this answer | |
  • Why the -1? Is something wrong? – rpax Jul 2 '15 at 9:53

You don't those /dev links to do this. I know sudo will purge environment variables, but you can still pass variables through in the form of arguments since you're calling another shell anyway...

sudo -u nobody bash -c '
    a=$1; echo "$a"
' -- "$(echo aaa)"


echo aaa | { 
    sudo -u nobody bash -c '
        a=$1; echo "$a"
    ' -- "$(</dev/fd/0)"

Both of these print:


...to stdout...

| improve this answer | |

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.