I want to make a script that starts a program and then sends it key input. In psuedo-script:
#!/bin/bash
./program << (PRESS CONTROL-Z)
The program is running so if there were additional commands in the script they will not be reached unless say control-z terminates the program.
Is this possible? From what I've found I thought it might require key codes but I could be wrong.
This I think is probably a better solution than "expect" since it can be executed in native bash script, I'll be interested to see what you think.
Use
`printf "character code here"`
note the backticks
So for instance I have written a script that controls a remote gnu screen session, the following line opens window 2 and issues the ctrl-c key combo
ssh -t user#$host screen -p 2 -X stuff `printf "\003"`
The -t option simulates terminal input on the remote machine
-p allows us to specify the name or number of the window we are connecting to within the screen session.
\003 is the bash format of character code 0x03
See here for a complete reference of codes.
To find the code of some key input you can use
printf "%#x\n" "'X"
0x58
Were X is the key you want to find the code of
To find codes of non literals you can use ctrl-v (makes bash append the next key to the command line rather than intepret it) and then type the key combo, so if I wanted to find the key code for ctrl-c I would delete the X press ctrl-v and then press ctrl-c.
One last thing the ascii code reference mentioned above lists 0x13 as the carriage return, but in the screen manual they list 0x15 as the enter key code, does anyone know why? Ive tested in a local screen and when I press enter 0x13 is produced, but when sending commands via ssh to a remote screen 0x13 doesn't work but 0x15 does.
Hope that helps
Piers
You might be looking for expect (from http://expect.nist.gov/). This deals with the complexities of pseudo-ttys that make it appear to the program that the input from the script (in this scenario, the expect program) is coming from a terminal.
Alternatively, you might be able to use echo or cat and pipe the output of that into the program - it depends on the program.
If you just want the program to start in the background, just do
#!/bin/bash
./program&
If your intent is to background the program, use:
./program & # The & sends the command to the background
echo commands here are executed while program is in the background
…
wait # Wait for the completion of background commands
echo commands here are executed after background program has completed
Edit: If your intent is to stop the program (as ctrl-Z often does in *nix shells), you can send it the STOP signal:
kill -STOP pid
To resume the execution, send it the CONT signal:
kill -CONT pid
In each of these examples pid is the process id of the program. If you launch it in a script, it's easy to get with the variable $!, e.g.
./prog &
echo prog started in the background
pid_of_prog=$!
kill -STOP $pid_of_prog
echo prog stopped
kill -CONT $pid_of_prog
echo prog continues
wait
echo prog finished
Edit 2: If your program is one that exits when it receives a ctrl-Z character, then remember that the control characters have the numerical value of the position letter in the alphabet (i.e. Ctrl-A is 1, Ctrl-B is 2, etc.). To send this character to a program you can:
echo -e "\032" | ./prog
(032 is 26, i.e. ^Z, in octal. Of course you can produce the same character by any means, perhaps adding it to the end of other input like ( cat inputfile ; echo -e "\032" ) | ./prog.
But this may not necessarily work; the program must be designed to recognise this character from the input (which it probably won't); usually the shell catches it. Then again, most programs reading input from stdin just exit when the input ends, so redirecting any finite input (even </dev/null) should cause it to terminate.
And, finally, if the intent was to stop the execution of the program when some other event (detected elsewhere in the script) has occurred, you can just kill it…
Related
I am executing sleep 10 && echo hello command on iTerm2.
I want to interrupt this command without waiting 10 seconds to print the word hello to the terminal. I have tried the key binding such as Command + C but it doesn't work.
What is the key binding for interrupting long running command such as sleep?
On macOS, too, you need to press ^ControlC (NOT ⌘CommandC⚠️) to interrupt a process in the shell.
Alternatively, in macOS's Terminal app (but not in iTerm), you can press ⌘Command. and it will be passed to the shell as the interrupt signal (^C).
(However, note that, if sleep 10 gets interrupted, echo hello will not get executed.)
I try to send a signal from one terminal A to another terminal B. Both run an interactive shell.
In terminal B, I trap signal SIGUSR1 like so :
$ trap 'source ~/mycommand' SIGUSR1
Now in terminal A I send a signal like so :
$ kill -SIGUSR1 pidOfB
Unfortunately, nothing happens in B. If I want to have my command executed, I need to switch to B and either input a new command or press enter.
How can I avoid this drawback and immediately execute my command instead ?
EDIT :
It's important to note that I want to interact directly with the interactive shell in terminal B from terminal A.
For this reason, every solution where the trap command would be executed in a subshell would not work for me...
Also, terminal B must stay interactive.
The shell may simply be stuck in a blocking read, waiting for command-line input. Hitting enter causes the handler to execute before the entered command. Running a non-blocking command like wait:
$ sleep 60 & wait
then sending the signal causes wait to terminate immediately, followed by the output of the handler.
Based on the answers and my numerous attempt to solve this, I don't think it's possible to catch a trap signal immediately in an interactive bash terminal.
For it to trigger, there must be an interaction from the user.
This is due to the readline program blocks until a newline is entered. And there is no way to stop this read.
My solution is to use dtach, a small program that emulate the detach feature of screen.
This program can run a fully interactive shell and features in its last version a way to communicate via a custom socket to this shell (or whatever program you launch)
To start a new dtach session running an interactive bash, in terminal B :
$ dtach -a /tmp/MySocket bash -i
Now from terminal A, we can send a message to the bash session in terminal B like so :
$ echo 'echo hello' | dtach -p /tmp/MySocket
In terminal B, we now see :
$ echo hello
hello
To expand on that if I now do in terminal A :
$ trap 'echo "cd $(pwd)" | dtach -p /tmp/MySocket' DEBUG
I'll have the directory of the two terminals synced
PS :I'd still like to know if there is a way to do this in pure bash
I use a similar trap so that periodically I can (from a separate cron job) force all idle bash processes to do a 'history -a'. I found that if I trap SIGALRM instead of SIGUSR1, then the bash blocking read seems not to be a problem: the trap runs now, rather than next time one hits return. I tried SIGINT, but that caused an annoying "^C", followed by a new prompt line, to be displayed. I haven't yet found any drawbacks of using SIGALRM, but perhaps they will arise.
It may be buffering.
As a test, try installing a loop trigger. In window A:
{ trap 'ls' USR1; while sleep 1; do echo>/dev/null;done } &
[1] 7316
in window B:
kill -usr1 7316
back in window A the ls is firing when the loop does an echo.
Don't know if that will help, but it's something.
I have some scripts that I'm developing on a vm but sometimes needs to run on a production server to be properly tested.
I need the output from the scripts for debugging so I've tinkered together the following solution:
function test_remote() {
scp $1 Prod:/home/xxx/tmp/
n=${1:t:r}
f=${1:t}
cmd="ssh Prod \"/usr/bin/php /home/xxx/tmp/$f\" > /home/xxx/tests/$n-remote-test.html"
eval ${cmd}
ssh Prod "rm /home/xxx/tmp/$f"
echo "done"
}
which I have placed in my .zshrc file
I would like to run it in the background using
test_remote path_to_file/php_file.php &
but as I do I always get the following result
[1] + 12996 suspended (tty input) test_remote path_to_file/php_file.php
if I resume it with bg it just repeats the same message
When a background process attempts to read from standard input, it is sent a signal which suspends it. This is so the user can bring the process to the foreground again and provide the necessary input.
If no input needs to be provided, you can redirect the standard input from /dev/null, either when calling test_remote or in cmd.
SSH is reading from its standard input, which is the terminal. It does this even if the program on the remote side doesn't try to read from its standard input, because it has no way to know that the remote side won't try (and also because the user could press a key such as Ctrl+C which the server side would translate to sending a signal).
There can only sensibly be a single process reading from the terminal: if there were multiple processes, which one would receive each key press? (When that does happen, the effect is that each byte goes more or less randomly to a different process.) The terminal management framework in the kernel ensures that (under normal circumstances) only the foreground process receives terminal input. If a background process tries to read from the terminal, it receives a SIGTTIN signal and the default action for that signal is to suspend the process. “12996 suspended (tty input)” is the shell letting you know that process 12996 was suspended by a SIGTTIN.
A generic way to avoid that, with any background command that might try to read from its standard input, is to redirect its standard input from somewhere else, for example /dev/null.
mycommand </dev/null &
With the SSH client, you can use the -n option to achieve the same effect. You can also use the -f option to tell ssh to go to the background after reading a password; this is useful if you have to use a password, but you should use keys instead if at all possible. These options have the downside that the background process is not a shell job so you can't wait for its termination and get.
Why on earth are you using eval? Just write
ssh Prod "/usr/bin/php /home/xxx/tmp/$f" > /home/xxx/tests/$n-remote-test.html
I have a game server running in an xterm window.
Once daily I'd like to send a warning message to any players followed after a delay by the stop command to the program inside the xterm window from a script running on a schedule. This causes the cleanup and save functions to run automatically.
Once the program shuts down I can bring it back up easily but I don't know how to send the warning and stop commands first.
Typed directly into xterm the commands are:
broadcast Reboot in 2 minutes
(followed by a 2 minute wait and then simply):
stop
no / or other characters required.
Any help?
Do you also need to type something from the xterm itself (from time to time) or do you want your server to be fully driven from external commands?
Is your program line-oriented? You may first try something like:
mkfifo /tmp/f
tail -f /tmp/f | myprogram
and then try to send commands to your program (from another xterm) with
echo "mycommand" > /tmp/f
You may also consider using netcat for turning your program to a server:
Turn simple C program into server using netcat
http://lifehacker.com/202271/roll-your-own-servers-with-netcat
http://nc110.sourceforge.net/
Then you could write a shell script for sending the required commands to your "server".
If you know a little about C programming; I remember having once hacked the script program (which was very easy to do: code is short) in order to launch another program but to read commands from a FIFO file (then again a shell script would be easy to write for driving your program).
Something you might try is running your program inside a screen session.
You can then send commands to the session from cron that will be just
as if you typed them.
In your xterm, before launching the program do:
screen -S myscreen bash
(or you can even replace bash by your program). Then from your cron
screen -S myscreen -X stuff 'broadcast Reboot in 2 minutes\n'
sleep 120
screen -S myscreen -X stuff 'stop\n'
will enter that text. You can exit the session using screen -S myscreen -X quit
or by typing ctrl-a \.
screen is intended to be transparent. To easily see you are inside screen, you can
configure a permanent status bar at the bottom of your xterm:
echo 'hardstatus alwayslastline' >~/.screenrc
Now when you run screen you should see a reverse video bottom line. Depending
on your OS it may be empty.
I want to write a bash script that will continue to run if the user is disconnected, but can be aborted if the user presses Ctrl+C.
I can solve the first part of it like this:
#!/bin/bash
cmd='
#commands here, avoiding single quotes...
'
nohup bash -c "$cmd" &
tail -f nohup.out
But pressing Ctrl+C obviously just kills the tail process, not the main body. Can I have both? Maybe using Screen?
I want to write a bash script that will continue to run if the user is disconnected, but can be aborted if the user presses Ctrl+C.
I think this is exactly the answer on the question you formulated, this one without screen:
#!/bin/bash
cmd=`cat <<EOF
# commands here
EOF
`
nohup bash -c "$cmd" &
# store the process id of the nohup process in a variable
CHPID=$!
# whenever ctrl-c is pressed, kill the nohup process before exiting
trap "kill -9 $CHPID" INT
tail -f nohup.out
Note however that nohup is not reliable. When the invoking user logs out, chances are that nohup also quits immediately. In that case disown works better.
bash -c "$cmd" &
CHPID=$!
disown
This is probably the simplest form using screen:
screen -S SOMENAME script.sh
Then, if you get disconnected, on reconnection simply run:
screen -r SOMENAME
Ctrl+C should continue to work as expected
Fact 1: When a terminal (xterm for example) gets closed, the shell is supposed to send a SIGHUP ("hangup") to any processes running in it. This harkens back to the days of analog modems, when a program needed to clean up after itself if mom happened to pick up the phone while you were online. The signal could be trapped, so that a special function could do the cleanup (close files, remove temporary junk, etc). The concept of "losing your connection" still exists even though we use sockets and SSH tunnels instead of analog modems. (Concepts don't change; all that changes is the technology we use to implement them.)
Fact 2: The effect of Ctrl-C depends on your terminal settings. Normally, it will send a SIGINT, but you can check by running stty -a in your shell and looking for "intr".
You can use these facts to your advantage, using bash's trap command. For example try running this in a window, then press Ctrl-C and check the contents of /tmp/trapped. Then run it again, close the window, and again check the contents of /tmp/trapped:
#!/bin/bash
trap "echo 'one' > /tmp/trapped" 1
trap "echo 'two' > /tmp/trapped" 2
echo "Waiting..."
sleep 300000
For information on signals, you should be able to man signal (FreeBSD or OSX) or man 7 signal (Linux).
(For bonus points: See how I numbered my facts? Do you understand why?)
So ... to your question. To "survive" disconnection, you want to specify behaviour that will be run when your script traps SIGHUP.
(Bonus question #2: Now do you understand where nohup gets its name?)