How does shell preserve command file input line boundary?

270 Views Asked by At

The description of STDIN of sh in SUSv4 2016 edition says

It shall not read ahead in such a manner that any characters intended to be read by the invoked command are consumed by the shell

I did an experiment to see it in action, by redirecting the following script file into sh -s:

#!/bin/sh
./rewind # a c program that reads up its stdin, prints it to stdout, before SEEK_SET its standard input to 0. 
echo 1234-1234

And it keeps printing out "echo 1234-1234". (Had shell consumed whole block of file, it would only print out "1234-1234")

So obviously, shells (at least my ones) do read at line boundaries.

But however, when I examined the FreeBSD ash "input.c" source codes, it reads in BUFSIZ-byte blocks, and I don't understand how it preserves line boundaries.

What I want to know is: How does shells preserve line boundaries when their source codes apparently shows that they read in blocks?

3

There are 3 best solutions below

0
DannyNiu On BEST ANSWER

When I compiled the FreeBSD ash with NO_HISTORY macro defined to 1 in "shell.h", it consumes the whole file and outputs only 1234-1234 on my rewind testing program. Apparently, the FreeBSD ash relies on libedit to preserve IO line boundaries.

0
clt60 On

Standard input isn't seekable in some cases, for example if it is redirected from a pipe or from a terminal. E.g. having a file called rew with a content:

#!/bin/bash
echo 123
perl -E 'seek(STDIN,0,0) or die "$!"' #the rewind

and using it as

bash -s < rew

prints

123
123
...
123
^C

so, when the STDIN is seekable it will work as expected, but trying the same from a pipe, such:

cat rew | bash -s   #the cat is intentional here :)

will print

123
Illegal seek at -e line 1.

So, your c-program rewind should print an error, when it is trying to seek in un-seekable input.

0
l0b0 On

You can demonstrate this (surprising) behaviour with the following script:

$ cat test.sh
cut -f 1
printf '%s\t%s\n' script file

If you pass it to standard input of your shell, line 2 onward should become the standard input of the cut command:

$ sh < test.sh
printf '%s\t%s\n' script file

If you instead run it normally and input literally foo, followed by Tab, bar, Enter, and Ctrl-d, you should instead see standard input being linked as normal:

$ sh test.sh 
foo     bar
foo
script  file

Annotated:

$ sh test.sh # Your command
foo     bar # Your input to `cut`
foo # The output of `cut`
script  file # The output of `printf`