6

/dev/null is a special Linux file which discards everything written to it, and which provides EOF when read.

I would like to read /dev/null to obtain and visualize this EOF. If I try:

$ cat /dev/null | hexdump

It doesn't work. The prompt returns with no output. EOF doesn't even have an ASCII code, so maybe this is the reason.

  1. Can EOF still be considered a character?

  2. Is there a way in bash to detect and print EOF, when provided in the stdin?

Something like:

$ cat /dev/null | some_tool
EOF

I'm using GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu) on Ubuntu 20.04.3, but I hope that the solution, if any, is not related to these specific versions.


This is not a homework, but just a way to learn more about EOF.

3
  • 1
    Related: What's the last character in a file? Sep 21, 2021 at 17:12
  • You mentioned "clarifying your problem" in the comments, but I can't see what the actual problem here would be. Is detecting the EOF (from /dev/null or any other source) actually important for you for trying to complete some concrete task? And what is that task?
    – ilkkachu
    Sep 21, 2021 at 18:07
  • 1
    @ilkkachu It is not for a specific task or homework. I just never dealt so much with EOF and wanted to make some practice about it.
    – BowPark
    Sep 21, 2021 at 18:16

4 Answers 4

10

Despite there being a series of "End of {something}" characters defined in the original ASCII control character set, EOF is not a character. EOF is a situation that can be detected because nothing more can be read from the input stream.

For example, this loop will continue until there is nothing more to read (i.e. EOF has been reached)

{
  while IFS= read -r line; do
    printf '%s\n' "Read: >> $line <<"
  done
  if [ -n "$line" ]; then
    printf '%s\n' "And some extra data after the last line: >> $line <<"
  fi
  echo 'No more'
}

Or more efficiently (also not choking on NUL bytes with some sed implementations), avoiding a shell loop entirely

LC_ALL=C sed 's/.*/Read: >> & <</'; echo 'No more'

(and if there's data after the last line that will prefix it to the No more line).

The default setting for a terminal is that the ^D character as sent by the terminal when you press Ctrl D is interpreted by the terminal driver to indicate EOF. It doesn't send that (or other) character to applications, though; it causes read()s on the tty device file to return with whatever there is to read, and if there was nothing, that's interpreted as EOF by the application (see stty -a and look for eof for what the current EOF character is for your terminal device). Shells or other applications that implement their own line editor usually treat that character in the same way (at least when on an empty line buffer).

3
  • Thank you. Maybe you forgot an = sign after IFS in the first version. I edited the question to clarify the problem I deal with.
    – BowPark
    Sep 21, 2021 at 17:24
  • Ok, IIUC it addresses (2) this way: there is no "direct method" to detect and print EOF, but it's possible to take some actions when it occurs, as you do in your scripts, so it can be detected in an indirect way.
    – BowPark
    Sep 21, 2021 at 18:19
  • The method for detecting an end of file is that there is nothing more to read Sep 21, 2021 at 21:24
5

What you get from /dev/null is just that the read() system call returns successfully with zero bytes read. That's the same as happens when trying to read() from (or after) the end of a file, and because of that, it's called "end-of-file".

But there are the odder cases too, like a terminal, which can return zero bytes on one call (if the user hits ^D on a empty line, or after another ^D), but then return more data on the next read. And datagram sockets, that may support zero-byte datagrams, and the system will return zero bytes for such a datagram, but still return the next datagram on the next call. Also of course you could read() from a file at the end, getting zero bytes, but then try the call again later, and this time find more data waiting. (That would be what an implementation of tail -f might do.)

You can't really "print EOF", since there's nothing to print. It's just the lack of data.

Knowing that cat exits on EOF, you could of course do something like

(cat /dev/null; echo EOF) | whatever...

to print something after the end. Not that it's very useful, IMO.

See:

0

/dev/null does not send EOF. It just disconnects. It closes the file. It simulates a file of zero length.

0

Logically, the concept of an EOF is generated by the I/O handler. An EOF signal usually has nothing to do with the data being handled.

The only way to "send an EOF" is to cause the reader to report that there is nothing to read in response to a request to read something.

Consider the following code where function Hi relays stdin until EOF and is used to replay the output of an echo command:

Hi() {
  while IFS= read -r -d $'\n' || [[ "$REPLY" ]]; do
    echo "Hi: $REPLY"
 done }
Hi < <(echo Hello)
Hi: Hello
  • <(command) causes bash to create a pipe (which is conceptually a file) from the output of the command supplied.
  • <(command) replaces itself with the unique filename of the pipe whose physical name gets echoed if you say:
echo <(echo Hi)
/dev/fd/63

Therefore, <(command) creates a file from its stdout and every file may have a logical EOF where its reader knows that no more data is available due to a physical end of data.

The pipe will be held open for as long as the command inside <() remains executing, holding the pipe open. When the <(command) finishes, the input end of the pipe is closed.

The bash read statement exits with error code 1 when it sees an EOF, which according to ilkkachu, is a successful system read() of zero bytes because that is what happens when there is no more data to read from a file.

The system read() call can hang without returning for as long as the reader of the file is still an active process. A timeout avoids forever hang. Physical drive startup and other connections can be very slow. Assuming an EOF due to timeout would be both slow and prone to error. Successful system read() of zero bytes=EOF.

while IFS= read -r -d $'\n' || [[ "$REPLY" ]]; do

The || (logical OR) test exists to handle the case when the file does not end with a carriage return. According to -d, the bash read will successfully return when it reads a carriage return. Bash read will fail with code 1 even if data was successfully read because failure, exit with non-zero code 1, is how bash read relays EOF.

Any program which relays information from an input to an output can choose to react to information it sees in its data stream.

The console tty (now pty or psuedo termimal) line reader/editor (ReadLine) reacts to many characters and escape sequences it reads from the keyboard which gives them their special meaning. For example: Control-H performs a backspace and Control-D sends an EOF signal (what it calls EOT or End Of Transmission) while the tty itself remains open and can send further information given another read.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .