Explanation
Lines in a shell script do not simulate keyboard input. Each statement is a command that must finish before the next is run, and the entire thing runs in the context of the parent shell.
In an interactive shell you can type script mylog
Enter, this gives you another (inner) shell and then you can type e.g. ls
Enter which executes ls
in the inner shell. Eventually you can exit the inner shell by typing exit
Enter. When script
sees the other shell exits, it exits. When the original (outer) shell sees script
exits, it continues: it shows you the prompt and reads what you type. The next command (e.g. cat mylog
) will be executed in the original shell.
As long as script
runs, the outer shell waits and does not try to read from the terminal (in general: from stdin; in case of an interactive shell stdin is often the terminal). script
reads from the terminal and relays input to the inner shell.
The same sequence of commands in a script being executed will make the shell executing the script run script mylog
and wait for it to exit, just like in the case of interactive usage. And just like in the case of interactive usage, the inner shell will run commands relayed by script
, i.e. whatever script
reads from its stdin (which is usually the terminal). The important difference is: the outer shell reads code from the regular file (i.e. not from stdin), but the inner shell can only get code from stdin. The inner shell has no access to the script.
You claim your script stops after script /home/x/Desktop/clamlog2.cat
. It does not stop. It waits for script
to exit; and script
waits for the inner shell to exit; and the inner shell waits for you to type something. The prompt from the inner shell most likely looks exactly like the prompt from the interactive shell you started from, so you may think the whole script has exited. No, it has not; you're in the inner shell inside script
inside the outer shell (interpreting your script file) and the original interactive shell is even more outer. Now if you type exit
then the script will continue and commands you intended for the inner shell will be executed by the shell interpreting the script, including the exit
line (cat …
will never be executed).
Flawed "solution"
By providing the script via stdin of the outer shell, you can kinda simulate keyboard input. Like this:
# But don't.
bash <./the_script
This bash
will read code from its stdin and when it invokes script …
, the tool will inherit the stdin, so by reading its stdin it will also read from the file. This method is flawed because:
bash
can and sometimes will (and in some cases must) read the file beyond script …
before it invokes script
. In effect script
may read the file (i.e. the inner shell may get the code) starting from a point you don't expect. After it exits, the shell interpreting the script will execute already read code it shouldn't have read in the first place.
- Even if
bash
reads exactly what you hope for, script
will probably read beyond exit
(in our case it will read the cat …
line). The inner shell will get exit
and it will exit as expected, but the outer shell won't see what script
has read excessively (cat …
won't be executed at all; neither the inner shell nor the outer shell will see it).
Solution: here document
It's way better to run the_script
normally. Inside it you can run script
with redirected stdin. This example the_script
uses a here document for redirection:
#!/bin/sh
script mylog <<EOF
ls
date
EOF
cat mylog
Now script
can read from the here document only (and you don't need exit
, the sole end of the here document should be enough). Note if instead of (or alongside) ls
or date
you had a command that reads from its stdin (e.g. another script
or ffmpeg
) then the problem of some tool reading too much would appear, like in the flawed "solution" above; it would be about reading too much from the here document, not from the entire script, but still.
If what you want to run inside script
does not read from stdin, or if you properly redirect stdin of whatever wants to read, then a here document may be a good solution.
Solution: script --command
Another solution is to provide code (to run in the inner shell) as an argument to script
. From man 1 script
:
-c, --command command
Run the command
rather than an interactive shell. This makes it easy for a script to capture the output of a program that behaves differently when its stdout is not a tty.
It's not explicitly stated, but command
is interpreted as shell code (i.e. not necessarily a single executable). Example:
#!/bin/sh
script mylog -c '
ls
date
'
cat mylog
Note the inner shell interpreting ls; date
is not an interactive shell. It won't print prompts and probably your aliases won't work.
Final note
There are other tools that give you a shell or a shell-like interface, or just read from stdin for whatever reason. Imagine instead of script …
you have bash
, ssh user@server
, python3
or even cat
. If you invoke any of it from a shell script, don't expect the next line(s) of the script to get to the tool. The mechanism is exactly like explained, it's not specific to script
. Our solution using a here document is universal. Our solution using script -c
is obviously specific to script
, but other tools may support a similar way to provide code/input as a command line argument (e.g. bash -c code
, ssh user@server code
).