Weird output when redirecting bash prompt to a file

Solution 1:

bash 2>file explained

Interactive Bash does use stderr to print the prompt (and the command line while it's being edited).

bash 2>file does not save any of these to the file because it's not interactive. Non-interactive bash does not print any prompt, does not evaluate PROMPT_COMMAND; there is no command line editing provided by readline and if the input is from the terminal then only basic editing provided by the terminal driver is available.

While Bash Reference Manual claims "an interactive shell is one started without non-option arguments […] and whose input and output are both connected to terminals […]", my tests indicate it's not about stdin and stdout (as I would interpret "input and output") but about stdin and stderr. I mean these start non-interactive shells:

  • <script bash
  • bash 2>file

and these start interactive shells:

  • bash >file
  • bash

With bash 2> file you started a non-interactive shell. bash did not print any prompt, that's why the file turned out to be empty.

If you did bash -i 2>file then you would enforce an interactive shell, you would send prompt(s) to the file. Try it, but expect no echo of what you type, because this goes to the file.

Similarly if you redirected stderr of already running interactive bash (exec 2>file) then you would also capture its prompt(s).


bash >file explained (in your case)

As stated above, bash >file starts an interactive shell. One sees prompts in the terminal, one can edit commands. Stderr is redirected as one expects.

In your case file contained extra strings because your PROMPT_COMMAND is what it is.

PROMPT_COMMAND
If this variable is set, and is an array, the value of each set element is interpreted as a command to execute before printing the primary prompt ($PS1). If this is set but not an array variable, its value is used as a command to execute instead.

(source)

This is your PROMPT_COMMAND:

printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"

When executed, it prints to stdout. If stdout is redirected to a file, it will print to the file.

When sent to a sufficiently advanced terminal (terminal emulator), a byte sequence resulted from \033]0;…\007 changes icon name and window title. In other words normally your terminal intercepts this output and uses it to configure itself rather then printing anything.

Probably you had deliberately set this PROMPT_COMMAND and forgot (or wasn't aware in the first place) it prints to stdout.

When stdout is redirected to a regular file then there will be no terminal to intercept the sequence and what printf from PROMPT_COMMAND prints will get to the file. You observed exactly this. It seems bat tried to do something with the ESC characters (from \033), ]0; and the first letter of kapil, that's why you saw apil; BEL characters resulting from \007 were shown as ^G. It doesn't matter. The point is what you didn't expect came from printf in your PROMPT_COMMAND.

If I were you and I wanted to keep this PROMPT_COMMAND, I would set it to printf … >&2. Normally stdout and stderr of an interactive shell go to the same terminal, so it doesn't matter which one printf (executed from PROMPT_COMMAND) prints to. But when you redirect stdout as you did, you don't want "garbage" from PROMPT_COMMAND.

Solution 2:

Redirecting will result in a file that is difficult to read (or use) because of the binary characters in the prompt.

For getting a readable complete copy of your session, you could use script(1) to log everything sent to the terminal:

$ script
Script started, file is typescript
$ # do your work
...
$ # then exit with ^D
$ exit
Script done, file is typescript