Ubuntu – How to pipe each command given to the shell

bashbashrccommand line

I'd like to edit my .bashrc so that every command executed on the shell is piped to something, for example:

 $ sudo apt update
  _________________
< sudo apt update >
 -----------------
    \   ^__^
     \  (oo)\_______
        (__)\       )\/\
            ||----w |
            ||     ||

I have managed something rather similar, but not entirely:

$ bash
$ exec > >(cowsay)
$ echo "Hello AU!"
$ exit
 _______
< Hello AU! >
 -------
    \   ^__^
     \  (oo)\_______
        (__)\       )\/\
            ||----w |
            ||     ||

It is not the desired result, as it only happens after exiting the current shell.

It's mainly for fun/learning purposes.

Best Answer

You can adapt your method a bit. Instead of piping to cowsay directly, read output till a delimiting character, send that output to cowsay, then print that character after every command:

exec > >(while IFS= read -d '' -r line; do if [[ -n $line ]]; then echo; printf "%s\n" "$line" | cowsay; fi; done)
PROMPT_COMMAND='printf "\0"'

Here, I'm using the ASCII NUL character. You can use something else that's unlikely to appear in command output.

This will print after the prompt, so the output will be ugly:

$ export LC_ALL=C
$ exec > >(while IFS= read -d '' -r line; do if [[ -n $line ]]; then echo; printf "%s\n" "$line" | cowsay; fi; done)
$ PROMPT_COMMAND='printf "\0"'
$ ls
$
 ______________________________________
/ Desktop Documents Downloads Music    \
| Pictures Public Templates Videos
\ examples.desktop                     /
 --------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

$ echo foo
$
 ______
< foo  >
 ------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Note that this will break any command which tries complex output or has a text user interface (think command line editors, pagers, etc.).

Assuming you already know what exec > >(...) does, the part in the process substitution is:

  • while IFS= read -d '' -r line; do ... done: this is a fairly common idiom for reading data delimited by the ASCII NUL character:

    • IFS= sets the IFS to the empty string, which disables field splitting
    • -r prevents read from treating \ in the input specially (so, \n for example, is read as \n and not converted to the newline character).
    • -d '' is the way to tell read to read until the NUL character

    So the whole thing loops over input in NUL-delimited sections, while preserving the contents of the input as much as possible.

  • if [[ -n $line ]]; then ... fi; done - only act if the input read so far is not empty.
  • echo; printf "%s\n" "$line" | cowsay; - print a leading empty line, so that the cowsay output doesn't clash with the prompt, and then send the input read so far to cowsay. printf is more reliable and safer than echo.
Related Question