how to set an expect variable with output of shell command - linux

I want to set a variable b in expect file,here initially i did ssh to a machine through this script,in that machine I want to do fetch a value and set the expect variable using following command:
set b [exec `cat /home/a |grep "work"|awk -F '=' '{print $2}'`]
send_user "$b"
file /home/a have following structure:
home=10.10.10.1
work=10.20.10.1
I am trying to use variable b after printing it
but after doing ssh script it is giving:
can't read "2": no such variable
while executing
If I put this output in a file name temp in that machine and try to do:
set b [exec cat ./temp]
then also it gives:
cat: ./temp: No such file or directory
If I do send "cat ./temp" it prints the correct output.
Please let me know where I am going wrong.

Single quotes are not quoting mechanism for Tcl, so brace your awk expressions.
% set b [exec cat /home/a | grep "work" | awk -F {=} {{print $2}}]
10.20.10.1
Reference : Frequently Made Mistakes in Tcl

Assuming you spawned an ssh session, or something similar, send "cat ./temp\r" shows you the file on the remote host, and exec cat ./temp shows you the file on the local host. exec is a plain Tcl command.
Capturing the command output of a send command is a bit of PITA, because you have to parse out the actual command and the next prompt from the output. You need to do something like:
# don't need 3 commands when 1 can do all the work
send {awk -F= '/work/ {print $2}' /home/a}
send "\r"
expect -re "awk\[^\n]+\n(.+)\r\n$PROMPT" # where "$PROMPT" is a regex that
# matches your shell prompt.
set command_output $expect_out(1,string)
send_user "$command_output\n"

Related

Escaping quotes in bash (Embedded awk)

I have a complex command I am passing via ssh to a remote server. I am trying to unzip a file and then change its naming structure and extension in a second ssh command. The command I have is:
ssh root#server1 "gzip -d /tmp/file.out-20171119.gz; echo file* | awk -F'[.-]' '{print $1$3".log"}'"
Obviously the " around the .log portion of the print statement are failing me. The idea is that I would strip the .out portion from the filename and end up with file20171119.log as an ending result. I am just a bit confused on the syntax or on how to escape that properly so bash interprets the .log appropriately.
The easiest way to deal with this problem is to avoid it. Don't bother trying to escape your script to go on a command line: Pass it on stdin instead.
ssh root#server1 bash -s <<'EOF'
gzip -d /tmp/file.out-20171119.gz
# note that (particularly w/o a cd /tmp) this doesn't do anything at all related to the
# line above; thus, probably buggy as given in the original question.
echo file* | awk -F'[.-]' '{print $1$3".log"}'
EOF
A quoted heredoc -- one with <<'EOF' or <<\EOF instead of <<EOF -- is passed literally, without any shell expansions; thus, $1 or $3 will not be replaced by the calling shell as they would with an unquoted heredoc.
If you don't want to go the avoidance route, you can have the shell do the quoting for you itself. For example:
external_function() {
gzip -d /tmp/file.out-20171119.gz
echo file* | awk -F'[.-]' '{print $1$3".log"}'
}
ssh root#server1 "$(declare -f external_function); external_function"
declare -f prints a definition of a function. Putting that function literally into your SSH command ensures that it's run remotely.
You need to escape the " to prevent them from closing your quoted string early, and you need to escape the $ in the awk script to prevent local parameter expansion.
ssh root#server1 "gzip -d /tmp/file.out-20171119.gz; echo file* | awk -F'[.-]' '{print \$1\$3\".log\"}'"
The most probable reason (as you don't show the contents of the root home directory in the server) is that you are uncompressing the file in the /tmp directory, but feeding to awk filenames that should exist in the root home directory.
" allows escaping sequences with \. so the correct way to do is
ssh root#server1 "gzip -d /tmp/file.out-20171119.gz; echo file* | awk -F'[.-]' '{print \$1\$3\".log\"}'"
(like you wrote in your question) this means the following command is executed with a shell in the server machine.
gzip -d /tmp/file.out-20171119.gz; echo file* | awk - F'[.-]' '{print $1$3".log"}'
You are executing two commands, the first to gunzip /tmp/file.out-2017119.gz (beware, as it will be gunzipped in /tmp). And the second can be the source for the problem. It is echoing all the files in the local directory (this is, the root user home directory, probably /root in the server) that begin with file in the name (probably none), and feeding that to the next awk command.
As a general rule.... test your command locally, and when it works locally, just escape all special characters that will go unescaped, after being parsed by the first shell.
another way to solve the problem is to use gzip(1) as a filter... so you can decide the name of the output file
ssh root#server1 "gzip -d </tmp/file.out-20171119.gz >file20171119.log"
this way you save an awk(1) execution just to format the output file. Or if you have the date from an environment variable.
DATE=`date +%Y%m%d`
ssh root#server1 "gzip -d </tmp/file.out-${DATE}.gz >file${DATE}.log"
Finally, let me give some advice: Don't use /tmp to uncompress files. /tmp is used by several distributions as a high speed temporary dir. It is normally ram based, too quick, but limited space, so uncompressing a log file there can fill up the memory of the kernel used for the ram based filesystem, which is not a good idea. Also, a log file normally expands a lot and /tmp is a local system general directory, where other users can store files named file<something> and you can clash with those files (in case you do searches with wildcard patterns, like you do in your command) Also, it is common once you know the name of the file to assign it to environment variables and use those variables, so case you need to change the format of the filename, you do it in only one place.

String starts again after every variable [Bash]

I'm trying to use multiple variables in a string but after each variable the string starts again and overrides the beginning:
#!/bin/bash
var1="ABCDEFG"
var2="hi"
echo "${var1} ${var2}"
echo "$var1 $var2"
It should output
ABCDEFG hi
But both echos output
hiDEFG
Also if I only use one variable and put text after the variable it still overrides...
There is also an example here:
https://stackoverflow.com/a/17862845/8363344
bla=hello
laber=kthx
echo "${bla}ohai${laber}bye"
This should output:
helloohaikthxbye
But it outputs:
byeikthx
I'm starting the .sh with
sudo bash path/bash.sh
But with sudo sh it does not work as well...
I use Ubuntu 16.04 (as a virtual machine)
Thanks
Dennis
It might be a carriage return character in your input string(s).
You can pipe your echo to a tr command to remove it from output string:
echo "${var1} ${var2}" | tr -d '\r'

Linux command and eof in one line

I want to ask if is possible to combine linux command and <
sendmail -S "lalalal" -f "dailaakak" -au "kakakak" <<EOF
>lalal:lalal
>opp:ttt
>ggg:zzz
EOF
I want to have something like that sendmail -S "lalalal" -f "dailaakak" -au "kakakak" <<EOF; lalal:lalal; opp:ttt; ggg:zzz; EOF
I need to use that not in bash script
If it has to be in one line without newlines use that:
echo -e "lalal:lalal\nopp:ttt\nggg:zzz" | sendmail -S "lalalal" -f "dailaakak" -au "kakakak"
echo -n interpretes escapes characters such as \n as a newline.
If you are asking whether you can use the << EOF in an interactive shell then the answer is yes, you can.
Note this functionality is called here document and that there can be any word instead of EOF. For example:
$ cat - << someword
> Here you
> can
> write text with as many
> newlines as you want.
> someword
Here you
can
write text with as many
newlines as you want.
(cat - prints whatever it receives on stdin)
For more information on here documents you can read for example this: http://tldp.org/LDP/abs/html/here-docs.html
I have tried and succeeded but it's messy. EOF simply does not like to accept substituted new lines for some reason so it needs to be put in another format. Now I'm sure this could be achieved with an expect script one one line but the below is what I have made and works.
echo "ssh localhost `printf "<< EOF\necho "Working!" >> /tmp/myfile \nEOF\n"`" > file.sh; chmod770 file.sh; ./file.sh
printf "<< EOF\necho Test! >> /tmp/myfile \nEOF\n" | xargs ssh localhost
Please ensure chmod file permissions are suitable for your own work case! Putting it into an environment variable instead of a file is also likely to work.

how to exec multiple commands from a single variable from a tcl file

I am trying to make a custom MOTD header using a tcl file. I've already been succuessful in adding the commands to the last line of the /etc/profile
cowsay -f $(ls /usr/share/cowsay/cows/ | shuf -n1) $(whoami), $(fortune)
I want to add this into my existing MOTD but I do not know the proper syntax to exec multiple commands without the pipe break command. As you can see below I have tried:
#!/usr/bin/env tclsh
# * Variable
set cows [exec -- /usr/bin/whoami | /usr/games/fortune | cowsay]
# * Output
puts "$cows"
which outputs the fortune and cowsay fine but, I cannot seem to get the whoami command to exec up with the other commands.
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Any help regarding how multiple commands are executed from within the syntax of the tcl format would greatly be appreciated, thanks y'all.
The answer by iagreen is of course correct and probably more maintainable, but to answer your subquestion: how to translate $(some command) into tcl:
In bash, the $(...) syntax executes the string captured by the parenthesis by evaluating it in another shell - a new instance of bash often referred to as a subshell.
In tcl, the exec command executes its arguments as a list of words to be evaluated in a subshell.
So, putting two and two together the correct translation of $(...) is [exec ...].
Therefore, the direct translation of this:
cowsay -f $(ls /usr/share/cowsay/cows/ | shuf -n1) $(whoami), $(fortune)
is this:
exec cowsay -f [exec ls /usr/share/cowsay/cows/ | shuf -n1] \
[exec whoami], [exec fortune]
Which is basically the same as the answer given by iagreen.
The problem with your approach is fortune ignores stdin.
It looks like you are building a string to pass to cowsay. Why not build the string in pieces using Tcl strings. For example,
#!/usr/bin/env tclsh
# * Variable
set cowsParam [exec /usr/bin/whoami]
append cowsParam ", " [exec /usr/games/fortune]
set cowImage [exec ls /usr/share/cowsay/cows/ | shuf -n1]
set cows [exec cowsay -f $cowImage $cowsParam]
# * Output
puts "$cows"

Setting an environment variable in csh

I have the following line at the first line in my script file:
#!/bin/sh
So I'm using csh.(?)
I wanto assign the output of the following to an environment variable:
echo $MYUSR | awk '{print substr($0,4)}'
I try:
set $MYVAR = echo $MYUSR | awk '{print substr($0,4)}'
But it doesn't work,
How can I do it? I want to do it in a sh file.
Your script should look like
#!/bin/csh
set MYVAR = `echo $MYUSR | awk '{print substr($0,4)}'`
echo $MYVAR
I don't have a way to test this right now, let me now if it doesn't work.
If you've inherited the basis of your script from someone else, with the #!/bin/sh,
then you have to find out if /bin/sh is really the bourne shell, or if it is a link to /bin/bash
You can tell that by doing
ls -l /bin/sh /bin/bash
if you get back information on files where the size is exactly the same, the you're really using bash, but called as /bin/sh
So try these 2 solutions
MYVAR=$(echo $MYUSR | awk '{print substr($0,4)}')
echo $MYVAR
AND
MYVAR=``echo $MYUSR | awk '{print substr($0,4)}``
echo $MYVAR
# arg!! only one pair of enclosing back-ticks needed,
# can't find the secret escape codes to make this look exactly right.
in all cases (csh) included, the back-ticks AND the $( ... ) are known as command substitution.
What every output comes from running the command inside, is substituted into the command line AND then the whole command is executed.
I hope this helps.
if it's /bin/sh it's bourne shell or bash, and use back quotes to execute something and this to assign that...
MYVAR=`echo $MYUSR | awk ...`
That script first line indicates that it should be interpreted by the Bourne shell (sh), not csh. Change it to
#!/bin/csh
The first line of your code shows clearly you are not using a csh. You are using a plain sh environment/shell. You have 2 options:
Either change the first line to #!/bin/csh OR
Keeping first line unchanged, update the code for setting the variable.
MYVAR=`echo $MYUSR | awk '{print substr($0,4)}`
echo $MYVAR
Let me know, if you get any error.

Resources