Tommy Stanton

Computer programmer and banjo picker

Standard output and standard error

TLDR (Too Long, Didn't Read)

I realize that this post is very long-winded, so here are the actual useful bits:

  • If a shell command is printing to STDERR, but you want to pipe it to a program, add 2>&1 just before the |.

  • Discard STDERR by adding 2> /dev/null to the end of the command.


I'm currently learning a song from Nintendo's Game & Watch Gallery (the "Fire" game) on Chapman Stick. To do so, I'm using the *.gbs file of the game.

I found out about the gbsplay project from the Wikipedia entry for *.gbs files. I decided to download the tarball and to try compiling it on my CentOS machine.

After a successful make in the source directory, I ran the newly-made gbsplay binary with the *.gbs for Game & Watch Gallery as the argument. Unfortunately, the TUI controls were quickly scrolled off screen due to errors that were spewing from one of the channels in the song I was playing:

$ pwd
/home/tstanton/software/source/gbsplay/gbsplay-0.0.91
$ ./gbsplay ~/tmp/Game\ \&\ Watch\ Gallery\ \(1997\)\(Nintendo\).gbs \
5
Bank 7 out of range (0-1)!
Bank 7 out of range (0-1)!
Bank 7 out of range (0-1)!
Bank 7 out of range (0-1)!
Bank 7 out of range (0-1)!
GBSVersion:     1
Title:          "Game & Watch Gallery"
Author:         "<?>"
Copyright:      "1997 Nintendo"
Load address:   0x10ab
Init address:   0x10ab
Play address:   0x11fe
Stack pointer:  0xdfef
File size:      0x00006fc5
ROM size:       0x00008000 (2 banks)
Subsongs:       15

commands:  [p]revious subsong   [n]ext subsong   [q]uit player
           [ ] pause/resume   [1-4] mute channel



^MSong   5/ 15 (Untitled)
00:00/02:00  ---       A-5 %%%   ---       ---       [    |    ]
Bank 7 out of range (0-1)!
Bank 7 out of range (0-1)!
^MSong   5/ 15 (Untitled)
00:00/02:00  ---       A-5 %%#   ---       ---       [   #|#   ]
Bank 7 out of range (0-1)!
Bank 7 out of range (0-1)!
^MSong   5/ 15 (Untitled)
00:00/02:00  ---       A-5 %%=   ---       ---       [   #|#   ]
...

I know how to discard STDERR, but I wanted to see if I could ask the program to simply not print the errors in the first place. I could see that there's a -q option that might do that:

$ ./gbsplay --help 
./gbsplay: invalid option -- -
Usage: ./gbsplay [option(s)] <gbs-file> [start_at_subsong [stop_at_subsong] ]

Available options are:
...
  -q        reduce verbosity
...

I now realize that the program does not take long options, only short. Passing a --help option was giving me the usage that I wanted, but it was doing so on STDERR (notice the brief error message that prints first).

Not knowing this, I proceeded to try to pipe the usage output to less so that I could have the text handy in a suspended job. It didn't work. I had a hunch that the usage information was being printed to STDERR, not to STDOUT. I couldn't quite remember what the shell syntax is to merge the streams, as you can see from my history:

$ history 300
...
 1400  ./gbsplay --help | less
 1401  ./gbsplay --help &2>1 | less
 1402  ./gbsplay --help &1>2 | less
 1403  ./gbsplay --help &2>1 | less
 1404  ./gbsplay --help 2&>1 | less
 1405  ./gbsplay --help 2>&1 | less
...

It took me 5 tries to get the syntax right (command #1405 pipes it to less successfully). Pretty embarrassing ("Hmm...maybe the ampersand goes...here!"). The last 2 times I literally just used CTRL-t to move the ampersand along it's merry way.. Additionally, a pleasant side effect of this is that I had accidentally littered my working directory with two files named 1 and 2 in my failed attempts.

So back to the original thing I was trying to solve. I added the -q option to my command to play the *.gbs, hoping that those errors would be suppressed. They weren't.

Time for more drastic measures. Having faith that those "out of range" errors were indeed being printed to STDERR, I knew that I could simply discard STDERR by piping it to the magical /dev/null file:

$ ./gbsplay ~/tmp/Game\ \&\ Watch\ Gallery\ \(1997\)\(Nintendo\).gbs \
5 2> /dev/null 
GBSVersion:     1
Title:          "Game & Watch Gallery"
Author:         "<?>"
Copyright:      "1997 Nintendo"
Load address:   0x10ab
Init address:   0x10ab
Play address:   0x11fe
Stack pointer:  0xdfef
File size:      0x00006fc5
ROM size:       0x00008000 (2 banks)
Subsongs:       15

commands:  [p]revious subsong   [n]ext subsong   [q]uit player
           [ ] pause/resume   [1-4] mute channel

Song   5/ 15 (Untitled)
00:03/02:00  ---       A-5 %%-   A#3 %%%   ---       [ #%%|%%# ]

I ended up not using -q, since it turned out that that option was removing the awesome ASCII art that the program displays about the note being played.

That worked! Problem solved. My TUI is intact. :) I am still curious as to why gbsplay is having trouble with playing some of the melody of this song though. :(

gbsplay source code investigation

I didn't actually realize that --help was incorrect until I looked at gbsplay.c, out of curiosity. I wanted to see where usage information was being printed to STDERR.

I found that if I had called the program with the correct -h option, the usage information would have been printed to STDOUT, as I expected.

gbsplay.c (licensed under GNU GPL)

static regparm void parseopts(int *argc, char ***argv)
{
        long res;
        myname = *argv[0];
        while ((res = getopt(*argc, *argv, "1234E:f:g:hlo:qr:R:t:T:vVzZ")) != -1) {
                switch (res) {
                default:
                        usage(1);
                        break;
...
                case 'h':
                        usage(0);
                        break;
...

...and we can see in the function definition that the usage text can be printed on either STDERR or STDOUT, depending on the exitcode argument given to usage().

static regparm void usage(long exitcode)
{
        FILE *out = exitcode ? stderr : stdout;
        fprintf(out,
                _("Usage: %s [option(s)] <gbs-file> [start_at_subsong [stop_at_subsong] ]\n"
                "\n"
                "Available options are:\n"
                "  -E        endian, b == big, l == little, n == native (%s)\n"
                "  -f        set fadeout (%ld seconds)\n"
                "  -g        set subsong gap (%ld seconds)\n"
                "  -h        display this help and exit\n"
...
blog comments powered by Disqus