Search This Blog

Friday, November 28, 2008

Advanced stdin, stdout and redirection


You've probably heard of stdout (standard out) and stdin (standard in) before but may not know what they really mean. Basically, when you run a program in the shell like 'ls' or 'grep' the output that it produces is sent to stdout, which is your terminal window. stdin is basically the opposite, instead of getting data from a file, stdin is opened up to a pipe for input to the program. If you have ever put '| less' after a command to control the amount of output you get at once, you've used stdin. By using the pipe symbol (|) you are redirecting the stdout from a program like 'ls' to the stdin of 'less'.

[user@host ~]$ ls -l | less
total 53
drwxr-xr-x   2 suso     suso         1024 Dec  7 19:10 ./
drwxr-xr-x   3 suso     suso         1024 Nov 27 06:32 ../
-rw-------   1 suso     suso        12288 Dec  7 19:21 .ioredirect.phtml.swp
-rw-r--r--   1 suso     suso         3763 Dec  6 03:44 bangcharacter.phtml
-rw-r--r--   1 suso     suso         1574 Dec  7 03:29 electricfence-readme
-rw-r--r--   1 suso     suso          127 Nov 27 06:56 footer.phtml
-rw-r--r--   1 suso     suso          219 Dec  6 00:58 header.phtml
-rw-r--r--   1 suso     suso         1496 Dec  7 03:51 index.phtml
-rw-r--r--   1 suso     suso         3132 Dec  7 19:10 ioredirect.phtml
-rw-r--r--   1 suso     suso         3132 Dec  7 02:49 jobcontrol.phtml
-rw-r--r--   1 suso     suso         5231 Dec  6 03:33 movement.phtml
-rw-r--r--   1 suso     suso         6842 Dec  7 19:09 regularexpressions.phtml
-rw-r--r--   1 suso     suso         3132 Dec  7 02:55 regularexpressions.phtml~
-rw-r--r--   1 suso     suso         3680 Dec  5 20:00 shellvariables.phtml
(END)

The output of ls is treated just like it was a file and you had typed less .

Another way you can redirect stdout is by using the angle operators, <>. If you want to send stdout to a file you use '> filename'. If you want to send the contents of a file to the stdin of a program, you can do '<>

[user@host ~]$ ls -l > listoutput
[user@host ~]$ cat listoutput
total 53
drwxr-xr-x   2 suso     suso         1024 Dec  7 19:10 ./
drwxr-xr-x   3 suso     suso         1024 Nov 27 06:32 ../
-rw-------   1 suso     suso        12288 Dec  7 19:21 .ioredirect.phtml.swp
-rw-r--r--   1 suso     suso         3763 Dec  6 03:44 bangcharacter.phtml
-rw-r--r--   1 suso     suso         1574 Dec  7 03:29 electricfence-readme
-rw-r--r--   1 suso     suso          127 Nov 27 06:56 footer.phtml
-rw-r--r--   1 suso     suso          219 Dec  6 00:58 header.phtml
-rw-r--r--   1 suso     suso         1496 Dec  7 03:51 index.phtml
-rw-r--r--   1 suso     suso         3132 Dec  7 19:10 ioredirect.phtml
-rw-r--r--   1 suso     suso         3132 Dec  7 02:49 jobcontrol.phtml
-rw-r--r--   1 suso     suso         5231 Dec  6 03:33 movement.phtml
-rw-r--r--   1 suso     suso         6842 Dec  7 19:09 regularexpressions.phtml
-rw-r--r--   1 suso     suso         3132 Dec  7 02:55 regularexpressions.phtml~
-rw-r--r--   1 suso     suso         3680 Dec  5 20:00 shellvariables.phtml
[user@host ~]$
[root@host /usr/src/linux]# patch -p1 <>

You can also append the output of a program to a file using double greater-than signs (>>).

[root@host ~]# tail /var/log/messages
Dec  7 18:22:51 host ftpd[26820]: FTP session closed
Dec  7 18:22:52 host ftpd[26821]: ACCESS DENIED (not in any class) TO dhcp-33.domain.net [xxx.xxx.xxx.xxx]
Dec  7 18:22:52 host ftpd[26821]: FTP LOGIN REFUSED (access denied) FROM dhcp-33.domain.net [xxx.xxx.xxx.xxx], xxxxxxxx
Dec  7 18:22:52 host ftpd[26821]: FTP session closed
Dec  7 18:23:04 host ftpd[26825]: FTP LOGIN FROM dhcp-33.domain.net [xxx.xxx.xxx.xxx], xxxxxxxx
Dec  7 18:23:06 host ftpd[26827]: FTP LOGIN FROM dhcp-33.domain.net [xxx.xxx.xxx.xxx], xxxxxxxx
Dec  7 18:26:29 host ftpd[26827]: FTP session closed
Dec  7 19:00:59 host PAM_pwdb[27479]: (su) session opened for user news by (uid=0)
Dec  7 19:01:01 host PAM_pwdb[27479]: (su) session closed for user news
Dec  7 19:59:48 host PAM_pwdb[28448]: (su) session opened for user root by suso(uid=500)
[root@host /root]# echo "Dec  7 20:00:30 host messaged: Hi Mom." >> /var/log/messages
[root@host /root]# tail /var/log/messages
Dec  7 18:22:52 host ftpd[26821]: FTP session closed
Dec  7 18:23:04 host ftpd[26825]: FTP LOGIN FROM dhcp-33.domain.net [xxx.xxx.xxx.xxx], xxxxxxxx
Dec  7 18:23:06 host ftpd[26827]: FTP LOGIN FROM dhcp-33.domain.net [xxx.xxx.xxx.xxx], xxxxxxxx
Dec  7 18:26:29 host ftpd[26827]: FTP session closed
Dec  7 19:00:59 host PAM_pwdb[27479]: (su) session opened for user news by (uid=0)
Dec  7 19:01:01 host PAM_pwdb[27479]: (su) session closed for user news
Dec  7 19:59:48 host PAM_pwdb[28448]: (su) session opened for user root by suso(uid=500)
Dec  7 20:01:00 host PAM_pwdb[28639]: (su) session opened for user news by (uid=0)
Dec  7 20:01:01 host PAM_pwdb[28639]: (su) session closed for user news
Dec  7 20:00:30 host messaged[]: Hi Mom.
[root@host /root]#

There is also stderr which with is like stdout, but is the erroneous output that comes out of a program. Sometimes you might be redirecting the output of a program and you will still see output in your terminal window, this is stderr output.

[root@host /root]# ls -l /var/log /var/log/httpd/access_og > logslist
ls: /var/log/httpd/access_og: No such file or directory
[root@host /root]#

That line of output that says that the file access_og doesn't exist was sent by the ls program to stderr, which the > doesn't catch. If you want to catch the output of stderr you have to use the n> notation to express which output filehandle number you wish to use. In the case of stderr, that number is 2.

[root@host /root]# ls -l /var/log /var/log/httpd/access_og 2> logslist
-rw-------   1 root     root      1463915 Dec  7 20:22 cron
-rw-r--r--   1 root     root         2574 Jan 30  2000 dmesg
-rw-r--r--   1 root     root            0 May  6  1999 htmlaccess.log
drwxr-xr-x   2 root     root        17408 Dec  1 00:04 httpd/
-rw-r--r--   1 root     root       157972 Dec  7 15:10 lastlog
-rw-------   1 root     root       688206 Nov 30 23:40 maillog
-rw-------   1 root     root       365531 Dec  7 20:11 messages
-rw-r--r--   1 root     root            0 Dec  1 00:04 netconf.log
-rw-------   1 root     root       561272 Dec  7 20:19 secure
-rw-r--r--   1 root     root       162275 Dec  7 04:23 spinwebd
-rw-------   1 root     root          337 Dec  7 00:04 spooler
-rw-rw-r--   1 root     utmp        92544 Dec  7 18:26 wtmp
-rw-------   1 root     root            0 Dec  1 00:04 xferlog
[root@host /root]# cat logslist
ls: /var/log/httpd/access_og: No such file or directory
[root@host /root]#

To get both the stdout and stderr to go to a file we use a combination of > and 2>&1, which means that we want to duplicate the output of file descriptor 1 and put it on file descriptor 2. So both stdout (1) and stderr(2) get sent to the file 'logslist'.

$ cat food 2>&1 > file cat: can't open food $ cat food file 2>&1 $

Although lots of sh manual pages don't mention this, the shell reads arguments from left to right.

  1. On the first command line, the shell sees 2>&1 first. That means "make the standard error (file descriptor 2) go to the same place as the standard output (fd1) is going." There's no effect because both fd2 and fd1 are already going to the terminal. Then >file redirects fd1 (stdout) to file. But fd2 (stderr) is still going to the terminal.

  2. On the second command line, the shell sees >file first and redirects stdout to file. Next 2>&1 sends fd2 (stderr) to the same place fd1 is going - that's to the file. And that's what you want.


No comments: