12

I am trying to redirect all output from bash (prompt, user input, results) to a file

Example:

/bin/bash > file.txt 2>&1

I thought that would work, but I'm not getting the prompt. Can anyone tell me what I'm doing wrong?

5 Answers5

9

Bash outputs the prompt only in interactive mode. I.e. it is normally output to the terminal (/dev/tty on linux). That is neither /dev/stdout or /dev/stdin :)

Now, I'm not sure but I can imagine that bash will allow limited interactive mode when there isn't a fully functional tty. In that case I'd expect the prompt to be written to stdout. I haven't tested that.

Nice Proof Of Concept:

(for a in some set of words; do echo $a > /dev/tty; done) 2>&1 > /dev/null

will just output 1..10 as if there wasn't redirection. Like the prompt, output is directly sent to the terminal (which will fail if there isn't one)

HINT: if you wanted everything to be collected look at

sehe
  • 635
  • Added hints on how to potentially get more bash output into a pipe – sehe Sep 15 '11 at 14:37
  • seq is a highly nonstandard external command, and shouldn't be used in this manner. If you're using bash, do something like for x in {1..10}, or for ((x=1; x<=10; x++)) instead. – Chris Down Sep 15 '11 at 23:25
  • @Chris: good point, thanks for the heads up – sehe Sep 15 '11 at 23:30
  • 2
    This is incorrect. The prompt is written to stderr, i.e. file descriptor 2, whether that is a TTY or not. This is easy enough to verify: bash -i > stdout.txt 2> stderr.txt and you'll see that the prompt is written to stderr.txt, not your terminal. – Dominick Pastore Jan 28 '21 at 18:11
6

The simplest way to do it would be

bash -i >/tmp/logfile 2>&1

Bash will write everything to /tmp/logfile and keep executing commands as you type them, but nothing will be displayed in the terminal. You can make it exit just as you exit your terminal session - by pressing Ctrl+D or typing exit.

Notice that if you run the same thing without stderr redirection, you will only have the greeting message logged to the file, all the rest will work in your current terminal. So the answer to your question about stream to which bash outputs its prompt (and all the following commands) seems to be: stderr.

Oh yes, and the -i parameter simply forces bash to run in interactive mode. Don't listen to those people - you don't need any magic tricks to do that ;)

5

The prompt is written to stderr as truss (on Solaris here) shows:

$ truss -ft write -p 10501
10501:  write(2, " d", 1)               = 1
10501:  write(2, " a", 1)               = 1
10501:  write(2, " t", 1)               = 1
10501:  write(2, " e", 1)               = 1
10501:  write(2, "\n", 1)               = 1
10521:  write(1, " S a t u r d a y ,   S e".., 46)  = 46
10501:      Received signal #18, SIGCLD [caught]
10501:        siginfo: SIGCLD CLD_EXITED pid=10521 status=0x0000
10501:  write(2, " $  ", 2)             = 2
jlliagre
  • 61,204
2

To trick bash into thinking it's in interactive mode (although stdout is not being sent to a terminal) you may use the already mentioned script command.

(
exec 1> >(tee bashlog.txt) 2>&1
script -q /dev/null /bin/bash -l
)

# alternative without script command
(
# bash: no job control in this shell
exec 1> >(tee bashlog.txt) 2>&1
/bin/bash -il
)
0

In interactive mode, bash outputs its prompt into stderr. The following strace command shows it (--norc option to make sure that ~/.bashrc which sets another prompt is not interpreted to overwrite my custom prompt):

$ export PS1="MYPROMPT> "
MYPROMPT> strace -f -- bash --norc 
execve("/usr/bin/bash", ["bash"], 0x7ffcd83805b0 /* 59 vars */) = 0
[...]
openat(AT_FDCWD, "/dev/tty", O_RDWR|O_NONBLOCK) = 3
[...]
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, TCGETS, {B38400 opost isig icanon echo ...}) = 0
[...]
write(2, "MYPROMPT> ", 10MYPROMPT> )              = 10 <----- prompt written into stderr (fd = 2)
pselect6(1, [0], NULL, NULL, NULL, {[], 8}

The same command with the redirection of stdout and stderr into a file shows that the prompt is not printed but the shell accepts the commands:

MYPROMPT> strace -f -- bash --norc > /tmp/shout 2> /tmp/sherr
date

Look at the files from another terminal. The content of shout is:

$ cat /tmp/shout
dim. 04 sept. 2022 16:19:24 CEST  <------- Result of the date command entered above

The content of sherr is:

$ cat /tmp/sherr
execve("/usr/bin/bash", ["bash", "--norc"], 0x7ffe73ea5da8 /* 59 vars */) = 0
[...]
openat(AT_FDCWD, "/dev/tty", O_RDWR|O_NONBLOCK) = 3
[...]
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, TCGETS, 0x7ffc43496b30)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]
read(0,  <-------- No prompt is displayed

But if you do the same adding -i option to the bash command line:

MYPROMPT> strace -f -- bash --norc -i > /tmp/shout 2> /tmp/sherr

The prompt is displayed on stderr:

$ cat /tmp/sherr
execve("/usr/bin/bash", ["bash", "--norc", "-i"], 0x7ffd8c1fa520 /* 59 vars */) = 0
[...]
ioctl(2, TCGETS, 0x7ffe1a8465f0)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]
ioctl(0, TIOCGWINSZ, {ws_row=37, ws_col=155, ws_xpixel=0, ws_ypixel=0}) = 0
[...]
write(2, "MYPROMPT> ", 10MYPROMPT> )              = 10  <------ Prompt displayed on stderr (fd = 2)
pselect6(1, [0], NULL, NULL, NULL, {[], 8}
Rachid K.
  • 172