I'm trying to remotely process a while read line loop via ssh with the following:
ssh /root/.ssh/id_rsa "while read var; do echo \"- $var -\"; done < /tmp/file; cat /tmp/file"
/tmp/file exists remotely. Output of the command should be:
- /tmp -
- /var -
/tmp
/var
But output actually is:
- -
- -
/tmp
/var
Why doesn't $var get filled here when remotely executed ?
If I happen to connect a console via ssh to the machine and execute the same command, it works as expected.
You should use single quotes or escape the $ in $var. Right now $var is getting substituted before ssh runs.
ssh /root/.ssh/id_rsa 'while read var; do echo "- $var -"; done < /tmp/file; cat /tmp/file'
$var is getting expanded before the ssh because it's inside double quotes. If you make the command you're passing to ssh be in single quotes it won't get expanded, or if you escape the $ you should be set:
ssh /root/.ssh/id_rsa 'while read var; do echo "- $var -"; done < /tmp/file; cat /tmp/file'
or
ssh /root/.ssh/id_rsa "while read var; do echo \"- \$var -\"; done < /tmp/file; cat /tmp/file"
Related
I want to change the name of a file if it is already present on a remote server via SSH.
I tried this from here (SuperUser)
bash
ssh user#localhost -p 2222 'test -f /absolute/path/to/file' && echo 'YES' || echo 'NO'
This works well with a prompt, echoes YES when the file exists and NO when it doesn't. But I want this to be launched from a crontab, then it must be in a script.
Let's assume the file is called data.csv, a condition is set in a loop such as if there already is a data.csv file on the server, the file will be renamed data_1.csv and then data_2.csv, ... until the name is unique.
The renaming part works, but the detection part doesn't :
while [[ $fileIsPresent!='false' ]]
do
((appended+=1))
newFileName=${fileName}_${appended}.csv
remoteFilePathname=${remoteFolder}${newFileName}
ssh pi#localhost -p 2222 'test -f $remoteFilePathname' && fileIsPresent='true' || fileIsPresent='false'
done
always returns fileIsPresent='true' for any data_X.csv. All the paths are absolute.
Do you have any idea to help me?
This works:
$ cat replace.sh
#!/usr/bin/env bash
if [[ "$1" == "" ]]
then
echo "No filename passed."
exit
fi
if [[ ! -e "$1" ]]
then
echo "no such file"
exit
fi
base=${1%%.*} # get basename
ext=${1#*.} # get extension
for i in $(seq 1 100)
do
new="${base}_${i}.${ext}"
if [[ -e "$new" ]]
then
continue
fi
mv $1 $new
exit
done
$ ./replace.sh sample.csv
no such file
$ touch sample.csv
$ ./replace.sh sample.csv
$ ls
replace.sh
sample_1.csv
$ touch sample.csv
$ ./replace.sh sample.csv
$ ls
replace.sh
sample_1.csv
sample_2.csv
However, personally I'd prefer to use a timestamp instead of a number. Note that this sample will run out of names after 100. Timestamps won't. Something like $(date +%Y%m%d_%H%M%S).
As you asked for ideas to help you, I thought it worth mentioning that you probably don't want to start up to 100 ssh processes each one logging into the remote machine, so you might do better with a construct like this that only establishes a single ssh session that runs till complete:
ssh USER#REMOTE <<'EOF'
for ((i=0;i<10;i++)) ; do
echo $i
done
EOF
Alternatively, you can create and test a bash script locally and then run it remotely like this:
ssh USER#REMOTE 'bash -s' < LocallyTestedScript.bash
Below is an example of a ssh script using a heredoc (the actual script is more complex). Is it possible to use both local and remote variables within an SSH heredoc or command?
FILE_NAME is set on the local server to be used on the remote server. REMOTE_PID is set when running on the remote server to be used on local server. FILE_NAME is recognised in script. REMOTE_PID is not set.
If EOF is changed to 'EOF', then REMOTE_PID is set and `FILE_NAME is not. I don't understand why this is?
Is there a way in which both REMOTE_PID and FILE_NAME can be recognised?
Version 2 of bash being used. The default remote login is cshell, local script is to be bash.
FILE_NAME=/example/pdi.dat
ssh user#host bash << EOF
# run script with output...
REMOTE_PID=$(cat $FILE_NAME)
echo $REMOTE_PID
EOF
echo $REMOTE_PID
You need to escape the $ sign if you don't want the variable to be expanded:
$ x=abc
$ bash <<EOF
> x=def
> echo $x # This expands x before sending it to bash. Bash will see only "echo abc"
> echo \$x # This lets bash perform the expansion. Bash will see "echo $x"
> EOF
abc
def
So in your case:
ssh user#host bash << EOF
# run script with output...
REMOTE_PID=$(cat $FILE_NAME)
echo \$REMOTE_PID
EOF
Or alternatively you can just use a herestring with single quotes:
$ x=abc
$ bash <<< '
> x=def
> echo $x # This will not expand, because we are inside single quotes
> '
def
remote_user_name=user
instance_ip=127.0.0.1
external=$(ls /home/)
ssh -T -i ${private_key} -l ${remote_user_name} ${instance_ip} << END
internal=\$(ls /home/)
echo "\${internal}"
echo "${external}"
END
I have below syntax that would compare the modified date of xml files against f1_mdate variable.
typeset -f mdate | ssh 101.101.101.101 "cd "$tar_dir"; $(cat); for xml_file in *.xml; do mdate_xml=$(mdate $xml_file) if [[ "$f1_mdate" == "$mdate_xml" ]]; then echo 1; fi; done"
xml_file: unbound variable
mdate_xml: unbound variable
can somebody please help point out why it is getting unbound variable?
I believe the problem is quoting; this will be clearer if I put line breaks where the shell sees closing quotes:
typeset -f mdate | ssh 10.225.28.45 "cd "\
$tar_dir"; $(cat); for xml_file in *.xml; do mdate_xml=$(mdate $xml_file) if [[ "\
$f1_mdate" == "\
$mdate_xml" ]]; then echo 1; fi; done"
In terms of how to write that correctly... that gets very tricky, because you have two layers of shell which are interpreting commands, stripping quotes and interpolating variables. I usually write a small shell script, scp it to the remote machine and run that remotely, just to get around that... or use a tool like ansible.
As an example of how to get data to and from the remote machine: I created /tmp/a.sh on the remote machine foo. Here's the content of /tmp/a.sh:
while read line
do
echo "xyzzy> $line"
done
On my local machine, I have /tmp/xyz containing
foo
bar
baz
Running
cat /tmp/xyz | ssh foo bash /tmp/a.sh
will give
xyzzy> foo
xyzzy> bar
xyzzy> baz
xyzzy>
You can re-direct the output to a file:
cat /tmp/xyz | ssh foo bash /tmp/a.sh > a.txt
The file a.txt will be on your local machine.
I need to read the variable from a remote file over SSH and compare it. But I get a variable in the wrong format. how to do it correctly?
#!/bin/bash
pass='dpassspass'
user='root#10.10.19.18'
IP="10.2.1.41"
path=/sys/variable/serv
#not work## No such file or directory# write=$(sshpass -p $ovhpass ssh -t $user echo "$IP" > $path)
sshpass -p $pass ssh -t $user << EOF
echo "$IP" > $path
EOF
my_var=$(sshpass -p $pass ssh -t $user "cd /sys_ovh; ./serv.bash")
echo mystart-"$my_var"-myend
read=$(sshpass -p $pass ssh -t $user cat $path)
echo start-"$read"-end
echo start-"$IP"-end
if [ "$read" == "$IP" ]; then
echo "run"
fi
output:
Connection to 10.10.19.18 closed.
-myendt-10.2.1.41
Connection to 10.10.19.18 closed.
-endt-10.2.1.41
start-10.2.1.41-end
Where I make a mistake? How to take data from the SSH?
The vars my_var and read are filled with a string ending with '\r', telling echo to go back to the first column. I think this is a problem with your local script. You can correct that with
tr -d "\r" < myfile > myfile2
Your fundamental problem comes from using unquoted here documents for the commands. You should properly understand in which order the shell interprets these contructs.
ssh remote cmd >file
executes cmd remotely, but first redirects the output from the ssh command to the local file.
ssh remote "cmd >’$file'"
The quotes cause the redirection to be part of the remote command line. The variable file is interpreted first, by the local shell, though.
ssh remote 'cmd >"$file"`
The single quotes prevent the local shell from modifying the command before sending it. Thus, he variable interpolation and the redirection are both handled by the remote shell, in this order.
So your commented-out "not work" command could easily be fixed with proper quoting. However, it will be much more elegant and efficient to use a single remote session, and execute all the commands in one go. Mixing the local variable IP with remote variables calls for some rather elaborate escaping, though. A major simplification would be to pass the value on standard input, so that the entire remote script can be single quoted.
#!/bin/bash
pass='dpassspass'
user='root#10.10.19.18'
IP="10.2.1.41"
result=$(echo "$IP" |
sshpass -p "$pass" ssh -t "$user" '
path=/sys/variable/serv
cat > "$path"
cd /sys_ovh
./serv.bash
cat "$path"')
echo mystart-"${result%$'\n'*}"-myend
echo start-"${result#*$'\n'}"-end
echo start-"$IP"-end
if [ "${result#*$'\n'}" == "$IP" ]; then
echo "run"
fi
The output from the remote shell is two lines; we pick it apart by using the shell's prefix and suffix substitution operators.
I work on a certain customer environment on a daily basis, comprised of 5 AIX servers, and sometimes I need to issue a same command on all 5 of them.
So I set up SSH key-based authentication between the servers, and whipped up a little ksh script that broadcasts the command to all of them:
#!/usr/bin/ksh
if [[ $# -eq 0 ]]; then
print "broadcast.ksh - broadcasts a command to the 5 XXXXXXXX environments and returns output for each"
print "usage: ./broadcast.ksh command_to_issue"
exit
fi
set -A CUST_HOSTS aaa bbb ccc ddd eee
for host in ${CUST_HOSTS[#]}; do
echo "============ $host ================"
if [[ `uname -n` = $host ]]; then
$*
continue
fi
ssh $host $*
done
echo "========================================="
echo "Finished"
Now, this works just fine, until I want to use a wildcard on the remote end, something like:
./broadcast.ksh ls -l java*
since the '*' is expanded on the local system as opposed to the remote.
Now, if using ssh remote commands, I can get around this by using single quotes:
ssh user#host ls -l java* <-- will _not_ work as expected, since asterisk will be interpreted locally
ssh user#host 'ls -l java*' <-- _will_ work as expected, since asterisk will be interpreted on the remote end
Now, I have tried to incorporate that into my script, and have tried to create a $command variable made up of the $* contents surrounded by single quotes, but have drowned in a sea of escaping backslashes and concatenation attempts in ksh, to no avail.
I'm sure there's a simple solution to this, but I'm not finding it so thought I would come out and ask.
Thanks,
James
As you found, passing an asterisk as an argument to your script doesn't work because the shell expands it before the arguments are processed. Try double-quoting $* and either escaping asterisks/semi-colons etc with backslashes in your script call, or single quoting the command.
for host in ${CUST_HOSTS[#]}; do
echo "============ $host ================"
if [[ `uname -n` = $host ]]; then
"$*"
continue
fi
ssh $host "$*"
done
$ ./broadcast.ksh ls -l java\*
$ ./broadcast.ksh 'ls -l java*; ls -l *log'
I wanted to comment but still too low on the totum, but Josh's single quote suggestion should work.
I spun up a couple of vms each with 2 files in /tmp : /tmp/foo1 and /tmp/foo2
then used a variation of your script
root#jdsdrop1:~# cat foo.sh
#!/usr/bin/ksh
if [[ $# -eq 0 ]]; then
print "broadcast.ksh - broadcasts a command to the 5 XXXXXXXX environments and returns output for each"
print "usage: ./broadcast.ksh command_to_issue"
exit
fi
set -A CUST_HOSTS jdsdropfed1 jdsdropfed2-2
for host in ${CUST_HOSTS[#]}; do
echo "============ $host ================"
if [[ `uname -n` = $host ]]; then
$*
continue
fi
ssh $host $*
done
echo "========================================="
echo "Finished"
root#jdsdrop1:~# ./foo.sh 'ls /tmp/foo*'
============ jdsdropfed1 ================
/tmp/foo1
/tmp/foo2
============ jdsdropfed2-2 ================
/tmp/foo1
/tmp/foo2
=========================================
Finished
root#jdsdrop1:~# ssh jdsdropfed1 "ls /tmp/foo*"
/tmp/foo1
/tmp/foo2
root#jdsdrop1:~# ssh jdsdropfed2-2. "ls /tmp/foo*"
/tmp/foo1
/tmp/foo2