I have a script in which I'm meticulously checking return codes for error conditions, so that I can abort early in the event of a failure. One step of this script involves running a command as the root user on another box, via ssh and sudo.
Consider:
ssh $HOST sudo $CMD
echo $?
ssh passes return codes back just fine, but even if $CMD returns a nonzero exit code, sudo still returns 0 after running the command.
How do I capture the return code to $CMD? I'm very partial to its being passed back as ssh's return code, but if there's another simple method which can't be confused by the output of $CMD, I'm all ears.
As it turns out, sudo is passing the return code properly. My test case very likely was overwriting $? with a subsequent execution or conditional test. I normally use a named variable to preserve $? to avoid such things, but there you are.
Related
I've got case: there's WordPress project where I'm supposed to create a script for updating plugins and commit source changes to the separated branch. While doing this I had run into a strange issue.
Input variable:
akimset,4.0.3
all-in-one-wp-migration,6.71
What I wanted to do was iterating over each line of this variable
while read -r line; do
echo $line
done <<< "$variable"
and this piece of code worked perfectly fine, but when I have added docker-compose logic everything started to act weirdly
while read -r line; do
docker-compose run backend echo $line
done <<< "$variable"
now only one line was executed and after this script exited with 0 and stopped iterating. I have found workaround with:
echo $variable > file.tmp
for line in $(cat file.tmp); do
docker-compose run backend echo $line
done
and that works perfectly fine and it iterates each line. Now my question is: why? ZSH and shell scripting could be a bit misterious and running in edge-cases like this one isn't anything new for me, but I'm wondering why succesfully executed script broke input stream.
The problem with this
while read -r line; do
docker-compose run backend echo $line
done <<< "$variable"
is that docker allocate pseudo-TTY. After the first execution of docker-compose run (first loop) it access to the terminal using up the next lines as input.
You have to pass -T parameter to 'docker-compose run' command in order to avoid docker allocating pseudo-TTY. Then, a working code is:
while read -r line; do
docker-compose run -T backend echo $line
done < $(variable)
Update
The above solution is for docker version 18 and docker-compose version 1.17. For newer version the parameter -T is not working but you can try:
-d instead of -T to run container in background mode BUT no you will not see stdout in terminal.
If you have docker-compose v1.25.0, in your docker-compose.yml add the parameter stdin_open: false to the service.
I was able to solve the same problem by using a different loop :
for line in $(cat $variable)
do
docker-compose run backend echo $line
done
I ran into a nearly identical problem about a year ago, though the shell was bash (the command/problem was also slightly different, but it applied to your issue). I ended up writing the script in zsh.
I'm not certain what's going on, but it's not actually the exit code (you can confirm by running the following):
variable=$'akimset,4.0.3\nall-in-one-wp-migration,6.71'
while read line; do docker-compose run backend print "$line"; print "$?"; done <<<($variable)
... which yielded ...
(akimset,4.0.3
0
(I'm not at all sure where the ( came from and perhaps solving that would answer why this problem happens)
Working Script
for line in "${(f)variable}"; do
docker-compose run backend echo "$line"
done
The (f) flag tells zsh to split on newlines; the "${(f)variable" is in quotes so that any blank lines aren't lost. If you're going to include escap sequences that you want to not be converted to the corresponding values (something that I often need when reading file contents from a variable), make the flags (fV)
I have hard requirement of logging into a terminal via SSH from TCL console and relaunch a tcl script from that terminal. For this I use exec command and it does get executed. The only problem is it doesn't return back to parent code.
I have automated SSH login and it works fine from a bash/csh terminal
But from TCL console, the following happens
Simple example
exec ssh hostname pwd
puts "Done"
When I execute this code in TCL, "Done" never gets printed. I just get the output of pwd and that's it.
I have a need of looping SSH into multiple terminals and run TCL jobs on a hardware, but the loop gets stuck after executing the first SSH.
I search the internet for answers and I am not able to find any. Please help.
There could be a lot issues going on here. Running ssh with an explicit command (pwd) will usually default to not allocating a tty (ssh -T) and will run the remote shell in non-interactive mode. And the output of a command called from exec is not normally echoed to standard output, so I would not expect you to see the output if you call it from a script. You have to print the result of exec to see the output of the pwd command. Also, different shell startup scripts are run on the remote host depending on which shell the account is set up with and whether it is an interactive or non-interactive shell. It could be .bashrc, .bash_profile, .profile, .cshrc, etc., and if the script behaves differently when it has a tty vs. when it doesn't, that could explain differing behavior between a bash/csh shell and the TCL console.
Without having access to your system, it is hard for me to troubleshoot. I would start with a script like this:
set result [exec ssh -T hostname pwd]
puts "result = $result"
puts "Done."
Then I would try changing the -T to a -t and trying again. If the output of "pwd" is appearing before the "result =" line, then you can tell that the command is writing the result to a tty instead of standard output, and that's useful information for troubleshooting.
I'm using external tool to run fuser -k 1099 command before actually launching my run configuration
But if external tool returns non-zero status, build configuration stops. That is perfectly correct, but I can not find any way to ignore failure. If it was a plain bash, I'd do something like fuser -k 1099 || true. But at Idea, that seems to be not possible
Any ideas?
You can use /bin/bash as the program and the following as the arguments:
-c 'fuser -k 1099'; true
This way the exit code of the tool will be always zero.
Correct answer was not working for me (see my comment under it) I then found a solution that is to create a script that exits with 0, here under windows (let us call it KillMyExeNoError.bat):
taskkill /IM my.exe /F
exit /B 0
Then put C:\Path\To\KillMyExeNoError.bat in Program and leave Arguments empty.
Maybe under Linux you need to put bash in Program and /path/to/script.sh in Arguments.
Not the best solution since it would be good not to have to create a separate script but at least it works.
I want to use rsync to my remote server for which I have SSH access. I use the following command:
rsync -e 'ssh -p 22222' -rtz --delete content_dir/ user#example.com:/home/user/public_html
After entering the command, it asks for the password for the remote location. When I type it, it exits with the message,
stdin: is not a tty
How do I supply the password to rsync? The method suggested should also work when I use it in a shell script.
You need to add:
[ -z "$PS1" ] && return
to the beginig of .bashrc that is located in your home dir.
The password is being accepted here, as you've stated yourself the operation does happen.
The error message "stdin: is not a tty" is due to something in the startup script on your server attempting to process an action that should only happen for interactive logins (when you connect with ssh direct to the server, etc).
[ -z "$PS1" ] && return solves the problem but it checks whether the prompt string length equals to zero and if it does then exits. Although $PS1 will not be set in a non-interactive shell, $PS1 being of zero length doesn't ultimately mean that the shell is not interactive.
Better approach is to check the shell's current options using $-. For example [[ $- != *i* ]] && return.
In case a simple return doesn't do the job, here's another approach taken from this blog article:
if `tty -s`; then
mesg n
fi
tty -s checks if there's a TTY attached (the -s tells it to do so silently and just exit with the appropriate return code). tty returns the tty attached (e.g. "/dev/pts/1"). This should be safer than checking some shell variable ;)
mesg controls the write access to your terminal (msg n disallows writing to the (in our case non-existing) terminal), and thus requires one to be present.
On some systems (in my case Debian Jessie, but there are also reports on Ubuntu) mesg n1 is set unconditionally in either ~/.bashrc or ~/.profile. So if it exists that way, this might be the culprit.
As with the other examples, you can of course make that a one-liner: [[ $(tty -s ) ]] && mesg n. And nobody keeps you from combining the two:
if [[ $(tty -s ) ]]; then
mesg n
else
return
fi
Btw: According to the linked article, this fragment should go to the .bashrc of the machine you connect to (the "remote") – so if that's johndoe#somehost, this should be applied at the start of /home/johndoe/.bashrc on somehost. In my case I only got rid of the message after having applied this change on the "calling host" as well.
PS: Also check the .profile if it has a stand-alone msg n command (it did in my case). If it does, wrap it there.
1: mesg n is used to prevent other users on the machine writing to your current terminal device, which per se is a good thing – but not helpful for some rsync job ;)
How do I execute a command every time after ssh'ing from one machine to another?
e.g
ssh mymachine
stty erase ^H
I'd rather just have "stty erase ^H" execute every time after my ssh connection completes.
This command can't simply go into my .zshrc file. i.e. for local sessions, I can't run the command (it screws up my keybindings). But I need it run for my remote sessions.
Put the commands in ~/.ssh/rc
You can put something like this into your shell's startup file:
if [ -n "$SSH_CONNECTION" ]
then
stty erase ^H
end
The -n test will determine if SSH_CONNECTION is set which happens only when logged in via SSH.
If you're logging into a *nix box with a shell, why not put it in your shell startup?
.bashrc or .profile in most cases.
Assuming a linux target, put it in your .profile
Try adding the command below the end of your ~/.bashrc. It should be exited upon logoff. Do you want this command only executed when logging off a ssh session? What about local sessions, etc?
trap 'stty erase ^H; exit 0' 0
You probably could setup a .logout file from /etc/profile using this same pattern as well.
An answer for us, screen/byobu users:
The geocar's solution will not work as screen will complain that "Must be connected to a terminal.". (This is probably caused by the fact that .ssh/rc is processed before shell is started. See LOGIN PROCESS section from man 8 sshd).
Robert's solution is better here but since screen and byobu open it's own bash instance, we need to avoid infinite recursion. So here is adjusted byobu-friendly version:
## RUN BYOBU IF SSH'D ##
## '''''''''''''''''' ##
# (but only if this is a login shell)
if shopt -q login_shell
then
if [ -n "$SSH_CONNECTION" ]
then
byobu
exit
fi
fi
Note that I also added exit after byobu, since IMO if you use byobu in the first place, you normally don't want to do anything outside of it.