Search code examples
bashpipehead

ps -ef | { head -n 1 ; sort ; } Unexpected output


Why does the following cut out the word root on the second line and skew the output, not to be uniform to all the following lines?

# ps -ef | { head -n 1 ; sort ; } | head -n11

UID        PID  PPID  C STIME TTY          TIME CMD
      470     2  0 Oct07 ?        00:00:00 [xfsaild/nvme4n1]
apache   10210  5801  0 Oct12 ?        00:04:04 /var/site/fastcgi.pl                                 
apache   10211  5801  0 Oct12 ?        00:03:11 /var/site/fastcgi.pl                                 
apache   10212  5801  0 Oct12 ?        00:03:35 /var/site/fastcgi.pl                                 
apache   10265  5801  0 Oct12 ?        00:03:55 /var/site/fastcgi.pl                                 
apache   10325  5801  0 Oct12 ?        00:03:50 /var/site/fastcgi.pl                                 
apache   10328  5801  0 Oct12 ?        00:03:39 /var/site/fastcgi.pl                                 
apache   10329  5801  0 Oct12 ?        00:02:59 /var/site/fastcgi.pl                                 
apache   10330  5801  0 Oct12 ?        00:03:50 /var/site/fastcgi.pl                                 
apache   11889  3815  0 Oct08 ?        00:33:11 /usr/sbin/httpd -k start

I'd expect output to print:

# ps -ef | { head -n 1 ; sort ; } | head -n2

UID        PID  PPID  C STIME TTY          TIME CMD
root       470     2  0 Oct07 ?        00:00:00 [xfsaild/nvme4n1]

This seems to be a Race condition. Sometimes when I run it it does not happen.

$ ps -ef | { head -n 1 ; sort ; } | head -n3
UID          PID    PPID  C STIME TTY          TIME CMD
avahi       1328       1  0 Oct12 ?        00:00:08 avahi-daemon: running [danied.local]
avahi       1401    1328  0 Oct12 ?        00:00:00 avahi-daemon: chroot helper

Solution

  • Your command is subject to race conditions, more specifically head -n1 could close the pipe behind before the second command manages to get any input, if the first command also has ended.

    Consider this example:

    > cat test.sh
    for ((i=1;i<=3; i++)); do
        printf "$i\n"
        # sleep 1
    done
    

    If you run this many times, you will get different results

    > sh test.sh | { head -n1; tail -n +1; }
    1
    > sh test.sh | { head -n1; tail -n +1; }
    1
    2
    3
    

    If you uncomment the sleep 1 statement into the loop, both commands will get input. Because the pipe will not be closed from the first command immediately, so the second part will wait for sure, although the first head is done.