running multiline bash command over ssh does not work - bash

I need to run a multi-line bash command over ssh, all possible attempt exhausted but no luck --
echo "3. All files found, creating remote directory on the server."
ssh -t $id#$host bash -c "'
if [[ -d ~/_tmp ]]; then
rm -rf ~/_tmp/*
else
mkdir ~/_tmp
fi
'" ;
echo "4. Sending files ..."
scp ${files[#]} $id#$host:~/_tmp/ ;
Here is the output --
user#linux:/tmp$ ./remotecompile
1. Please enter your id:
user
2. Please enter the names of the files that you want to compile
(Filenames *must* be space separated):
test.txt
3. All files found, creating remote directory on the server.
Password:
Unmatched '.
Unmatched '.
Connection to host.domain.com closed.
Please note, I do not want to put every 2-3 lines of bash if-then-else-fi commands into separate files.
What is the right way to do it?

Use an escaped heredoc to have its literal contents passed through. (Without the escaping, ie. using just <<EOF, shell expansions would be processed locally -- making for more interesting corner cases if you used variables inside your remotely-run code).
ssh "$id#$host" bash <<'EOF'
if [[ -d ~/_tmp ]]; then
rm -rf ~/_tmp/*
else
mkdir ~/_tmp
fi
EOF
If you want to pass arguments, doing so in an unambiguously correct manner gets more interesting (since there are two separate layers of shell parsing involved), but the printf '%q' builtin saves the day:
args=( "this is" "an array" "of things to pass" \
"this next one is a literal asterisk" '*' )
printf -v args_str '%q ' "${args[#]}"
ssh "$id#$host" bash -s "$args_str" <<'EOF'
echo "Demonstrating local argument processing:"
printf '%q\n' "$#"
echo "The asterisk is $5"
EOF

This works for me:
ssh [hostname] '
if [[ -d ~/_tmp ]]; then
rm -rf ~/_tmp
else
mkdir ~/_tmp
fi
'

Related

dealing with spaces in filenames and paths when compare local files to remote files (and delete remote if same)

The target directories should be variables, but that is an easy fix later. The target directories are locally (where the command is run from) and remote ("/home/user/somepath/"). Compare all files in target directories and subdirs and delete the remote ones on a match. Not sue if it actually looks at files in the target dirs themselves, but that can be dealt with later. Ideally this would compare more than 1 layer deep, but can recursively call if need be from within the subdirs. Or maybe the way I'm going about this is all wrong, if so please tell me of a different way.
The problem is spaces. Replacing " " with "\ " causes file not found. If the path OR the filename has a space the below tends to work, but if they both have space(s) bash breaks it up into multiple arguments.
echo;
for d in */; do
for fn in "$d"/*; do
if [ $(sha512sum "$fn" | cut -d ' ' -f 1) == $(ssh user#host sha512sum "/home/user/somepath/$fn" | cut -d ' ' -f 1) ];
then echo deleting "$fn"; ssh user#host rm "/home/user/somepath/$fn"; echo gone;
else echo diff;
fi;
done;
echo;
done
Same space problem, but this one check for file even existing on remote before hashing locally to speed up. This way I should be able to compare local1 to remote1 and remote2 more easily (like if you copied stuff from both remote1 and remote2 into local1).
echo;
for d in */; do
for fn in "$d"/*; do
if [[ $(ssh user#host test "/home/user/somepath/$fn") ]];
then if [ $(sha512sum "$fn" | cut -d ' ' -f 1) == $(ssh user#host sha512sum "/home/user/somepath/$fn" | cut -d ' ' -f 1) ];
then echo deleting "$fn"; ssh user#host rm "/home/user/somepath/$fn"; echo gone;
else echo diff;
fi;
else echo remote dont got $fn;
fi;
done; echo;
done
Not sure if it matters or not, but say dir inside targetdir is exdir1 and filename is exfile.ext, it prints out "exdir1//exfile.ext" when it echos $fn. It works with the //, but maybe it has some impact on the space problem.
For the ssh commands, change "/home/user/somepath/$fn" to " '/home/user/somepath/$fn' ". The space between " and ' is optional which is only to improve readibility.
You can try playing with it like:
ssh user#host touch " '/tmp/foo bar' "
ssh user#host ls " '/tmp/foo bar' "
ssh user#host rm " '/tmp/foo bar' "

Shellscript - Show error for specific argument when using mv

I'm currently writing code for a script to move files to a "dustbin".
As it stands - the code works fine but I want it to produce a message when a file has been moved correctly and a message when a specific file has failed to move/doesn't exist.
My code will accept multiple filenames for input, e.g.
# del test1.txt *.html testing.doc
# Successfully moved to Dustbin
However if only one of these does not exist it still produces an error message.
How do I do this but still allow it to accept arguments as seen in the above example?
My code is as follows:
#!/bin/sh
mv -u "$#" /root/Dustbin 2>/dev/null
# END OF SCRIPT
Sorry for what is probably an obvious question! I'm completely new to shellscript !
Many thanks
You would have to iterate over the arguments and try to move each one:
for path in "$#"; do
if mv -u "$path" /root/Dustbin 2>/dev/null; then
echo "Success"
else
printf 'Failed to move %s\n' "$path"
fi
done
As a shorthand for iterating over the arguments you can omit in "$#" like
for path; do
if mv -u "$path" /root/Dustbin 2>/dev/null; then
echo "Success"
else
printf 'Failed to move %s\n' "$path"
fi
done

Check if a file exists on a remote server with spaces in path

I'm trying to write a script to send my music from my computer to my android phone via ssh but I'm having some difficulties. Here is the piece of code :
while read SONG
do
echo $SONG
ssh -qi ~/.ssh/key root#$ip [[ -f "/sdcard/Music/${SONG}" ]] && echo "File exists" || echo "File does not exist"
done < ~/.mpd/playlists/Blues.m3u
The goal is to check if the song is already in the phone, and if it's not I'll scp it (if it's there it is with the same relative path than in the .m3u file).
I always got sh: syntax error: 'Davis/Lost' unexpected operator/operand and I think it is because there is a space before Davis that I can't escape
(the first $SONG is Geater Davis/Lost Soul Man/A Sad Shade Of Blue.mp3)
I also tried this, same result
while read SONG
do
echo $SONG
SONG="/sdcard/Music/$SONG"
echo $SONG
ssh -qi ~/.ssh/key root#$1 [[ -f "$SONG" ]] && echo "File exists" || echo "File does not exist"
done < ~/.mpd/playlists/Blues.m3u
Ideas welcome !!!
ssh command accepts only one argument as an command, as described in synopsis of manual page:
SYNOPSIS
ssh [...] [user#]hostname [command]
You need to adhere with that if you want correct results. Good start is to put whole command into the quotes (and escape any inner quotes), like this:
ssh -qi ~/.ssh/key root#$1 "[[ -f \"$SONG\" ]] && echo \"File exists\" || echo \"File does not exist\""
It should solve your issue.

Variable issues in SSH

Hey guys I'm trying to run this code:
#!/bin/bash
sudo /usr/local/bin/sshpass -p pwd ssh -o stricthostkeychecking=no -p 11022 admin#$1.test.com<<EOI
i=1
while read line
do
location="sudo sed -n ${i}p /Users/Shared/$1.txt"
number="sudo sed -n ${i}p /Users/Shared/$2n.txt"
my_array=("${my_array[i]}" $line)
sudo cp /Applications/TEPS\ OS\ X\ Share\ Folder/MAIN\ IMAGES\ FOLDER\ ƒ/${location}${number} /Users/Shared/FYP/$number
sudo sips -Z 256 /Users/Shared/FYP/$number /Users/Shared/FYP/$number
((i++))
done </Users/Shared/$2.txt
exit
EOI
basically it reads a text file which gives the location of certain images, and will create a thumbnail of those images, which can be downloaded later. The problem is that I need the value of $i to set the values of $location and $number, but when I set the variable within the while loop the variables are not set. I've tried setting it locally and globally with single quotes, double quotes, passing through with the sshpass, exporting it -This works as a test but $i is of course unknown- tried placing brackets, curly braces, parentheses, escaping $, at this point I have exhausted my ideas, it's probably something incredibly simple, but I could use a fresh pair of eyes, any help is greatly appreciated!
EDIT:
Thanks to Charles Duffy for helping me clean it up so this is what I have now:
#!/bin/bash
sudo /usr/local/bin/sshpass -p support ssh -o stricthostkeychecking=no -p 11022 admin#$1.noerrpole.com<<'EOI'
i=1
while read -r line
do
location=sudo sed -n ${i}p "/Users/Shared/$1.txt"
number=sudo sed -n ${i}p "/Users/Shared/$2n.txt"
my_array+=( "$line" )
sudo cp "/Applications/TEPS\ OS\ X\ Share\ Folder/MAIN\ IMAGES\ FOLDER\ ƒ/${location}${number}" "/Users/Shared/FYP/$number"
sudo sips -Z 256 "/Users/Shared/FYP/$number" "/Users/Shared/FYP/$number"
((i++))
exit
done <"/Users/Shared/$2.txt"
EOI
But now $2 isn't getting passed through to the loop here's what I get back
1:bin Photo$ bash -x thumb npco2 20131216154714
+ sudo /usr/local/bin/sshpass -p support ssh -o stricthostkeychecking=no -p 11022 admin#npco2.noerrpole.com
Pseudo-terminal will not be allocated because stdin is not a terminal.
SHPA_12-16-2013/
sed: /Users/Shared/n.txt: No such file or directory
cp: /Applications/TEPS OS X Share Folder/MAIN IMAGES FOLDER ƒ/ is a directory (not copied).
Warning: /Users/Shared/FYP/ not a valid file - skipping
Warning: /Users/Shared/FYP/ not a valid file - skipping
Error 4: no file was specified
Try 'sips --help' for help using this tool
So where $2 should equal 20131216154714 it's returning an empty string like this
sed: /Users/Shared/n.txt: No such file or directory
The correct command would be
sed: /Users/Shared/20131216154714n.txt
The rest is just failing because $2 isn't passed.
Again thanks for the help!
ssh ... <<EOI does expansion on the local end, before starting ssh. Use ssh ... <<'EOI' to do expansions on the remote end.
If you want to pass arguments, use printf '%q ' to quote them so they survive remote unescaping intact:
printf -v quoted_args '%q ' "$one" "$two"
ssh user#host "bash -s - ${quoted_args}" <<<'EOI'
...
EOI

Bash - Escaping SSH commands

I have a set of scripts that I use to download files via FTP and then delete them from the server.
It works as follows:
for dir in `ls /volume1/auto_downloads/sync-complete`
do
if [ "x$dir" != *"x"* ]
then
echo "DIR: $dir"
echo "Moving out of complete"
# Soft delete from server so they don't get downloaded again
ssh dan#172.19.1.15 mv -v "'/home/dan/Downloads/complete/$dir'" /home/dan/Downloads/downloaded
Now $dir could be "This is a file" which works fine.
The problem I'm having is with special characters eg:
"This is (a) file"
This is a file & stuff"
tend to error:
bash: -c: line 0: syntax error near unexpected token `('
bash: -c: line 0: `mv -v '/home/dan/Downloads/complete/This is (a) file' /home/dan/Downloads/downloaded'
I can't work out how to escape it so both the variable gets evaluated and the command gets escaped properly. I've tried various combinations of escape characters, literal quotes, normal quotes, etc
If both sides are using bash, you can escape the arguments using printf '%q ', eg:
ssh dan#172.19.1.15 "$(printf '%q ' mv -v "/home/dan/Downloads/complete/$dir" /home/dan/Downloads/downloaded)"
You need to quote the whole expression ssh user#host "command":
ssh dan#172.19.1.15 "mv -v /home/dan/Downloads/complete/$dir /home/dan/Downloads/downloaded"
I'm confused, because your code as written works for me:
> dir='foo & bar (and) baz'
> ssh host mv -v "'/home/dan/Downloads/complete/$dir'" /home/dan/Downloads/downloaded
mv: cannot stat `/home/dan/Downloads/complete/foo & bar (and) baz': No such file or directory
For debugging, use set -vx at the top of the script to see what's going on.
Will Palmer's suggestion of using printf is great but I think it makes more sense to put the literal parts in printf's format.
That way, multi-command one-liners are more intuitive to write:
ssh user#host "$(printf 'mkdir -p -- %q && cd -- "$_" && tar -zx' "$DIR")"
One can use python shlex.quote(s) to
Return a shell-escaped version of the string s
docs

Resources