15

When writing a bash script. Sometimes you are running a command which opens up another program such as npm, composer.. etc. But at the same time you need to use read in order to prompt the user.

Inevitable you hit this kind of error:

read: read error: 0: Resource temporarily unavailable

After doing some research there seems to be a solution by piping the STDIN of those programs which manipulate the STDIN of your bash script to /dev/null.

Something like:

npm install </dev/null

Other research has shown it has something to do with the fact that the STDIN is being set to some sort of blocking/noblocking status and it isn't being reset after the program finishes.

The question is there some sort of fool proof, elegant way of reading user prompted input without being affected by those programs that manipulate the STDIN and not having to hunt which programs need to have their STDIN redirected to /dev/null. You may even need to use the STDIN of those programs!

tshepang
  • 12,111
  • 21
  • 91
  • 136
CMCDragonkai
  • 6,222
  • 12
  • 56
  • 98
  • Can you post an example of a script that triggers this problem? It seems to be related to the fact that two separate processes are reading from the same file handle; even if you don't get the "Resource temporarily unavailable" error, you are open to logic errors because you have two (or more) processes consuming the same stream, and you won't be able to predict which process receives what from the stream. – chepner Nov 11 '13 at 16:24
  • 1
    I have solved the problem by redirecting the STDIN. It was composer, npm and bower. Point is, I don't really want to do that every time I write a complex bash script, just wanted to know if there was a generic solution. – CMCDragonkai Nov 11 '13 at 16:27
  • https://stackoverflow.com/a/16402813/2859065 – Luchostein Feb 06 '19 at 13:47

6 Answers6

9

Usually it is important to know what input the invoked program expects and from where, so it is not a problem to redirect stdin from /dev/null for those that shouldn't be getting any.

Still, it is possible to do it for the shell itself and all invoked programs. Simply move stdin to another file descriptor and open /dev/null in its place. Like this:

exec 3<&0 0</dev/null

The above duplicates stdin file descriptor (0) under file descriptor 3 and then opens /dev/null to replace it.

After this any invoked command attempting to read stdin will be reading from /dev/null. Programs that should read original stdin should have redirection from file descriptor 3. Like this:

read -r var 0<&3

The < redirection operator assumes destination file descriptor 0, if it is omitted, so the above two commands could be written as such:

exec 3<&0 </dev/null
read -r var <&3
spbnick
  • 5,025
  • 1
  • 17
  • 22
  • Here's a situation, I have a bash script that executes 3 programs. The first could be read, the second be npm and the third could be bower. The read needs to read from the command line's prompt, the npm's STDIN needs to be redirected to /dev/null, but bower also needs to read from STDIN. Is there a way to achieve this, so that if I extend the script later in the future (with further potential user prompts), I won't meet any problems with file descriptors getting closed by prior programs? – CMCDragonkai Nov 11 '13 at 22:24
  • 1
    One program cannot close another program's file descriptors, so this is not the problem. The problem is most likely some program leaving O_NONBLOCK on stdin "open file description" (which is shared) enabled. This happens rarely and the program doing this should be fixed. Workarounds could be employed such as redirecting the input from /dev/null, supplying it via additional pipe, or resetting O_NONBLOCK afterwards. I would recommend applying a workaround for that misbehaving program specifically and otherwise proceeding normally. – spbnick Nov 11 '13 at 22:57
  • How can I reset the O_NONBLOCK after I ran the program? – CMCDragonkai Nov 12 '13 at 06:30
  • See http://stackoverflow.com/a/16402813/1161045 You can probably do something similar with an interpreted language such as Perl or Python, which would not require compilation nor having a separate file for the reset program. – spbnick Nov 12 '13 at 09:09
  • I see, so there's no way to reset O_NONBLOCK from bash itself? – CMCDragonkai Nov 13 '13 at 00:24
  • @CMCDragonkai There doesn't seem to be. Not without modifying Bash itself. – spbnick Nov 13 '13 at 08:05
8

When this happens, run bash from within your bash shell, then exit it (thus returning to the original bash shell). I found a mention of this trick in https://github.com/fish-shell/fish-shell/issues/176 and it worked for me, seems like bash restores the STDIN state. Example:

bash> do something that exhibits the STDIN problem
bash> bash
bash> exit
bash> repeat something: STDIN problem fixed
Oliver
  • 27,510
  • 9
  • 72
  • 103
  • 2
    I have to do this often in Ubuntu (Ubuntu 18.04.5 LTS). Very annoying, but starting a new bash script and exiting has always worked. Glad to find out here that it isn't just me, and this "bash" trick is sanctioned (:-)) – Donna Aug 25 '20 at 01:14
6

I had a similar issue, but the command I was running did need a real STDIN, /dev/null wasn't good enough. Instead, I was able to do:

TTY=$(/usr/bin/tty)
cmd-using-stdin < $TTY
read -r var

or combined with spbnick's answer:

TTY=$(/usr/bin/tty)
exec 3<&0 < $TTY
cmd-using-stdin
read -r var 0<&3`

which leaves a clean STDIN in 3 for you to read and 0 becomes a fresh stream from the terminal for the command.

tshepang
  • 12,111
  • 21
  • 91
  • 136
zenwheel
  • 61
  • 1
  • 4
4

I had the same problem. I solved by reading directly from tty like this, redirecting stdin:

read -p "Play both [y]? " -n 1 -r </dev/tty

instead of simply:

read -p "Play both [y]? " -n 1 -r

In my case, the use of exec 3<&0 ... didn't work.

3

Clearly (resource temporarily unavailable is EAGAIN) this is caused by programs that exits but leaves STDIN in nonblocking mode. Here is another solution (easiest to script?):

perl -MFcntl -e 'fcntl STDIN, F_SETFL, fcntl(STDIN, F_GETFL, 0) & ~O_NONBLOCK'
niry
  • 3,238
  • 22
  • 34
2

The answers here which suggest using redirection are good. Fortunately, Bash's read should soon no longer need such fixes. The author of Readline, Chet Ramey, has already written a patch: http://gnu-bash.2382.n7.nabble.com/read-may-fail-due-to-nonblocking-stdin-td18519.html

However, this problem is more general than just the read command in Bash. Many programs presume stdin is blocking (e.g., mimeopen) and some programs leave stdin non-blocking after they exit (e.g., cec-client). Bash has no builtin way to turn off non-blocking input, so, in those situations, you can use Python from the command line:

$ python3 -c $'import os\nos.set_blocking(0, True)'

You can also have Python print the previous state so that it may be changed only temporarily:

$ o=$(python3 -c $'import os\nprint(os.get_blocking(0))\nos.set_blocking(0, True)')
$ somecommandthatreadsstdin
$ python3 -c $'import os\nos.set_blocking(0, '$o')'
hackerb9
  • 1,545
  • 13
  • 14