Your terminal runs a shell (most probably bash
), which is the one asking for your commands and runs them.
Besides of this interactive mode you can also use your shell to run commands from a file. To execute the commands in your file you can either call the shell directly like bash script
or you can start your file with a "shebang" #!/bin/bash
and make it executable (chmod +x script
). Then you can treat the script like a binary and execute it for example as ./script
or put it at a place where you shell is looking for it. (echo $PATH
)
Most probably both your interactive shell and the shell used to run is bash
.
From the perspective of a "first day learning linux" bash works exactly the same in both modes. - Much later you might stumble about slight differences. If you really want to know about in detail I would suggest reading man bash
and search for places mentioning "interactive". (You can search a man page, by pressing /
.)
One important thing to note is that the script is run as a new process. This especially means that variables set in the script are not visible in the calling shell.
$ export a=1
$ echo $a
1
$ bash -c 'echo $a;a=2;echo $a' # change the value in a script
1
2
$ echo $a # value not changed here
1
$
Without the export a
is not even visible to the inner script.
Following this, one can very well make that last plan of yours work. For the command to-be-sent not to be processed by the shell, it has to be in the form of a string when reaches the pipe (thus echo "command"
, not echo `command`
). Then it has to be read by a background process (alike a daemon, but not necessarily) started in the appropriate terminal. It should be evaluated by the same process.
But it is boiler-platey to have a script per pipe. So let's generalize making a script as term-pipe-r.sh
(don't forget to chmod +x
it!):
#!/bin/bash
pipe=$1 # the pipe name is the first argument
trap 'rm -f "$pipe"' EXIT # ignore exit and delete messages until the end
if [[ ! -p $pipe ]]; then # if the pipe doesn't exist, create it
mkfifo $pipe
fi
while true # cycle eternally..
do
if read line <$ pipe; then
if [[ "$line" == 'close the term-pipe pipe' ]]; then
break
# if the pipe closing message is received, break the while cycle
fi
echo # a line break should be used because of the prompt
eval $line # run the line: as this script should be started
fi # in the target terminal,
done # the line will be run there.
echo "<pipe closing message>" # custom message on the end of the script
So say you want /dev/tty3
to receive commands: just go there, do
./term-pipe-r.sh tty3pipe & # $1 will be tty3pipe (in a new process)
And to send commands, from any terminal (even from itself):
echo "command" > tty3pipe
or to run a file there:
cat some-script.sh > tty3pipe
Note this piping ignores files like .bashrc
, and the aliases in there, such as alias ls='ls --color'
. Hope this helps someone out there.
Edit (note - advantage of non-daemon):
Above I talked about the pipe reader not being a daemon necessarily, but in fact, I checked the differences, and it turns out it is way better to be a mere background process in this case. Because this way, when you close the terminal, an EXIT
signal (SIGHUP
, SIGTERM
, or whatever) is received by the script as well, and the pipe is deleted then (see the line starting with trap
in the script) automatically, avoiding a useless process and file (and maybe others if there were such redirecting to the useless pipe).
Edit (automation):
Still, it is boring to have to run a script you (I, at least) probably want most of the time. So, let's automatize it! It should start in any terminal, and one thing all of them read is .bashrc
. Plus, it sucks to have to use ./term-pipe-r.sh
. So, one may do:
cd /bin # go to /bin, where Bash gets command names
ln -s /directory/of/term-pipe-r.sh tpr # call it tpr (terminal pipe reader)
Now to run it you'd only need tpr tty3pipe &
in /dev/tty3
whenever you'd want. But why do that when you can have it done automatically? So this should be added to .bashrc
. But wait: how will it know the pipe name? It can base the name on the TTY (which can be know with the tty
command), using simple REGEX's in sed
(and some tricks). What you should add to ~/.bashrc
will then be:
pipe="$(sed 's/\/dev\///' <<< `tty` | sed 's/\///')pipe"
# ^^^- take out '/dev/' and other '/', add 'pipe'
tpr $pipe & # start our script with the appropriate pipe name
Best Answer
If you're using
bash(1)
, you can use thecompgen
builtin:-a
for aliases,-b
for builtins,-c
for commands and-A function
for shell functions. You can choose which if these you want to exclude, as you'll get a rather large list (on my system I get 3206 commands).The
compgen
command is meant for generating command line completion candidates, but can be used for this purpose too.