Expect script fails on foreach loop - loops

I have an expect script that cycles through a file with a list of hosts, ssh's into each one, sudo's to root, and runs id. The problem is it never returns to the source servers shell. It attempts to run from the host it has ssh'd into, thus messing up the logic.
set file [open "hosts.test"]
set hosts [split [read -nonewline $file] "\n"]
close $file
foreach host $hosts {
puts $host
spawn ssh -q -o StrictHostKeyChecking=no [lindex $argv 0]#$host
expect "Password: "
send "[lindex $argv 1]\r"
expect -re "(>|#) "
send "sudo su -\r"
expect "Enter YOUR password: "
send "[lindex $argv 1]\r"
expect -re "(>|#) "
send "id\r"
expect -re "(>|#) "
send "exit\r"
expect -re "(>|#) "
send "logout\r"
close $spawn_id
}
Here is the error:
send: sending "logout\r" to { exp4 }
server.blah.com
spawn ssh -q -o StrictHostKeyChecking=no user#server
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {21856}
expect: does "" (spawn_id exp5) match glob pattern "Password: "? no
Password:
expect: does "Password: " (spawn_id exp5) match glob pattern "Password: "? yes
expect: set expect_out(0,string) "Password: "
expect: set expect_out(spawn_id) "exp5"
expect: set expect_out(buffer) "Password: "
send: sending "passwordhere\r" to { exp5 }
Gate keeper glob pattern for '(>|#) ' is ''. Not usable, disabling the performance booster.
expect: does "" (spawn_id exp5) match regular expression "(>|#) "? (No Gate, RE only) gate=yes re=no
expect: does "\r\n" (spawn_id exp5) match regular expression "(>|#) "? (No Gate, RE only) gate=yes re=no
Password:
expect: does "\r\nPassword: " (spawn_id exp5) match regular expression "(>|#) "? (No Gate, RE only) gate=yes re=no
expect: timed out
send: sending "sudo su -\r" to { exp5 }
expect: does "\r\nPassword: " (spawn_id exp5) match glob pattern "Enter YOUR password: "? no
expect: does "\r\nPassword: \r\n" (spawn_id exp5) match glob pattern "Enter YOUR password: "? no
Password:
expect: does "\r\nPassword: \r\nPassword: " (spawn_id exp5) match glob pattern "Enter YOUR password: "? no
expect: timed out
send: sending "passwordhere\r" to { exp5 }
Gate keeper glob pattern for '(>|#) ' is ''. Not usable, disabling the performance booster.
expect: does "\r\nPassword: \r\nPassword: " (spawn_id exp5) match regular expression "(>|#) "? (No Gate, RE only) gate=yes re=no
expect: does "\r\nPassword: \r\nPassword: \r\n" (spawn_id exp5) match regular expression "(>|#) "? (No Gate, RE only) gate=yes re=no
expect: read eof
expect: set expect_out(spawn_id) "exp5"
expect: set expect_out(buffer) "\r\nPassword: \r\nPassword: \r\n"
send: sending "id\r" to { exp5 send: spawn id exp5 not open
while executing
"send "id\r""
("foreach" body line 11)
invoked from within
"foreach host $hosts {
puts $host
spawn ssh -q -o StrictHostKeyChecking=no [lindex $argv 0]#$host
expect "Password: "
send "[lindex $argv 1]\r"
e..."
(file "./bootstrap.exp" line 19)

Related

Use an array to dynamically assign a variable name, then extract array data while avoiding eval

I am doing a major script re-write. Part of that is removing eval for the usual reasons one might avoid eval. I am running into trouble finding a viable way of managing the following scenario type.
Consider these two eval statements:
eval echo '${'#${role}'[#]}' users
loc_NagiosUsersAll+=($( eval echo '${'${role}'[#]}' " " ))
The first prints to screen the number of users within a given role. The second adds all of those users to a larger array.
Role is to be whatever the current role being assessed happens to be. Let's call it read_only. We could write that first statement as follows then:
printf "${#read_only[#]} users"
I have tried dozens of combinations of parentheses, quotation marks, and various acrobatics to ditch eval and get those to work.
Here is the echo echo version (using one of the actual roles) for comparison:
$ echo echo '${'#${role}'[#]}' users
echo ${#authorized_for_all_host_commands[#]} users
$ echo ${#authorized_for_all_host_commands[#]} users
6 users
$ eval echo '${'#${role}'[#]}' users
6 users
I've managed to ditch all the other eval statements but this type is dug in like a tick.
So, how can I do this more securely than using eval?
More code...
declare -a NagiosUserRolesAll=( authorized_for_read_only
authorized_for_all_services
authorized_for_all_hosts
authorized_for_system_information
authorized_for_configuration_information
authorized_for_system_commands
authorized_for_all_service_commands
authorized_for_all_host_commands )
function func_NagiosUserDataGet(){ # was load_data_tables
local -a loc_NagiosUsersAll=""
printf "Loading users into the different tables. \n"
for role in "${NagiosUserRolesAll[#]}"
do
declare -ag $role="($( cat ${svnFilePath} | sed -n "s/${role}=//p" | sed 's/,/ /g' ))"
declare -n ref="${role}" # copy the reference, not the contents of the array
printf "The role ${role} has ${#ref[#]} users. \n"
loc_NagiosUsersAll+=(${ref[#]})
loc_NagiosUsersAll+=" "
done
printf "Creating list of unique users. \n"
NagiosUsersAllClean=($( echo ${loc_NagiosUsersAll[#]} | tr ' ' '\n' |
sort -u ))
printf "Total users: ${#NagiosUsersAllClean[#]}. \n"
}
function func_NagiosUsersShow(){ # was show_all_users
if [[ "${svnFileExists}" == '1' ]] ; then
printf "You'll need to checkout a cgi.cfg file first. \n"
return 1
fi
printf "\nThese are the roles with their users. \n\n"
for role in "${NagiosUserRolesAll[#]}"
do
# declare -ng ref="${role}" # copy the reference, not the contents of the array
printf "These users are in ${const_TextRed}${role}"
printf "${const_TextPlain}: "
printf "${const_TextGreen}"
# printf "${ref[#]} \n" # FAILS
printf "${ref[*]} \n" # ALSO FAILS (prints one user for each role)
# eval echo '${'${role}'[#]}' # WORKS
printf "${const_TextPlain} \n"
done
printf "\nNow for a list of unique users. \n\n"
func_EnterToContinue
printf "Unique users list: \n"
for i in "${!NagiosUsersAllClean[#]}"
do
printf "$i: ${NagiosUsersAllClean[$i]} \n"
done
func_EnterToContinue
}
With bash 4.3 or later, you can declare a variable as a reference to another variable by saying declare -n varref. Here's an example code:
#!/bin/bash
declare -a read_only=(Alice Bob Charlie)
declare -a authorized_for_all_host_commands=(Dan Emma Fred George Harry Irene)
declare -a loc_NagiosUsersAll
declare -n role="read_only"
echo ${#role[#]} users
# yields "3 users"
loc_NagiosUsersAll+=(${role[#]})
declare -n role="authorized_for_all_host_commands"
echo ${#role[#]} users
# yields "6 users"
loc_NagiosUsersAll+=(${role[#]})
echo ${#loc_NagiosUsersAll[#]} users
# yields "9 users"
echo ${loc_NagiosUsersAll[#]}
# yields "Alice Bob Charlie Dan Emma Fred George Harry Irene"
Hope this helps.
[Edited]
The following code is the modified version based on your latest post.
declare -a NagiosUserRolesAll=( authorized_for_read_only
authorized_for_all_services
authorized_for_all_hosts
authorized_for_system_information
authorized_for_configuration_information
authorized_for_system_commands
authorized_for_all_service_commands
authorized_for_all_host_commands )
function func_NagiosUserDataGet(){ # was load_data_tables
local -a loc_CaptureUsersPerRole=""
local -a loc_NagiosUsersAll=""
printf "Loading users into the different tables. \n"
for role in "${NagiosUserRolesAll[#]}"; do
declare -a $role="($( cat ${svnFilePath} | sed -n "s/${role}=//p" | sed 's/,/ /g' ))"
printf "These users have the role ${role}: "
declare -n ref=$role # copy the reference, not the contents of the array
printf "${#ref[#]} users \n"
loc_NagiosUsersAll+=(${ref[#]})
# loc_NagiosUsersAll+=" "
done
printf "Creating list of unique users. \n"
NagiosUsersAllClean=($( echo ${loc_NagiosUsersAll[#]} | tr ' ' '\n' | sort -u ))
printf "Total users: ${#NagiosUsersAllClean[#]}. \n"
}
[Edited on May 12]
The point is that the assignment to a reference should appear in the declare -n syntax. Otherwise it will yield an unexpected result. Here's the example:
declare -a arys=(ary_a ary_b ary_c)
declare -a ary_a=(a1 a2 a3)
declare -a ary_b=(b1 b2 b3)
declare -a ary_c=(c1 c2 c3)
# test 1
for role in "${arys[#]}"; do
declare -n ref="$role"
echo "${ref[#]}"
done
# => works properly
# test 2
for role in "${arys[#]}"; do
declare -n ref
ref="$role"
echo "${ref[#]}"
done
# => does not work correctly
[Edited on May 15]
Here's the modified verion which should work:
declare -a NagiosUserRolesAll=( authorized_for_read_only
authorized_for_all_services
authorized_for_all_hosts
authorized_for_system_information
authorized_for_configuration_information
authorized_for_system_commands
authorized_for_all_service_commands
authorized_for_all_host_commands )
function func_NagiosUserDataGet(){ # was load_data_tables
local -a loc_NagiosUsersAll=""
printf "Loading users into the different tables. \n"
for role in "${NagiosUserRolesAll[#]}"
do
declare -ag $role="($( cat ${svnFilePath} | sed -n "s/${role}=//p" | sed 's/,/ /g' ))"
declare -n ref="${role}" # copy the reference, not the contents of the array
printf "The role ${role} has ${#ref[#]} users. \n"
loc_NagiosUsersAll+=(${ref[#]})
loc_NagiosUsersAll+=" "
done
printf "Creating list of unique users. \n"
NagiosUsersAllClean=($( echo ${loc_NagiosUsersAll[#]} | tr ' ' '\n' |
sort -u ))
printf "Total users: ${#NagiosUsersAllClean[#]}. \n"
}
function func_NagiosUsersShow(){ # was show_all_users
if [[ "${svnFileExists}" == '1' ]] ; then
printf "You'll need to checkout a cgi.cfg file first. \n"
return 1
fi
printf "\nThese are the roles with their users. \n\n"
for role in "${NagiosUserRolesAll[#]}"
do
declare -ng ref="${role}" # copy the reference, not the contents of the array
printf "These users are in ${const_TextRed}${role}"
printf "${const_TextPlain}: "
printf "${const_TextGreen}"
# printf "${ref[#]} \n" # FAILS
printf "${ref[*]} \n" # => should work
# eval echo '${'${role}'[#]}' # WORKS
printf "${const_TextPlain} \n"
done
printf "\nNow for a list of unique users. \n\n"
func_EnterToContinue
printf "Unique users list: \n"
for i in "${!NagiosUsersAllClean[#]}"
do
printf "$i: ${NagiosUsersAllClean[$i]} \n"
done
func_EnterToContinue
}
The final working version of the two functions is as follows. I'm not clear why the printf lines needed that precise formatting, but there you have it.
function func_NagiosUserDataGet(){ # was load_data_tables
local -a loc_NagiosUsersAll=""
printf '%s\n' "Loading users into the different tables. "
for role in "${NagiosUserRolesAll[#]}"
do
declare -ag "${role}"="($( cat ${svnFilePath} | sed -n "s/${role}=//p" | sed 's/,/ /g' ))"
declare -n ref="${role}" # copy the reference, not the contents of the array
printf "The role ${role} has ${#ref[#]} users. %s\n"
loc_NagiosUsersAll+=("${ref[#]}")
loc_NagiosUsersAll+=(" ")
done
printf '%s\n' "Creating list of unique users. "
NagiosUsersAllClean=($( echo "${loc_NagiosUsersAll[#]}" | tr ' ' '\n' | sort -u ))
printf "There are ${#NagiosUsersAllClean[#]} total users. %s\n"
}
function func_NagiosUsersShow(){ # was show_all_users
if [[ "${svnFileExists}" == '1' ]] ; then
printf '%s\n' "You'll need to checkout a cgi.cfg file first. "
return 1
fi
printf '%s\n' "" "These are the roles with their users. " ""
for role in "${NagiosUserRolesAll[#]}"
do
declare -n ref="${role}" # copy the reference, not the contents of the array
printf "The role ${const_TextRed}${role}${const_TextPlain} has these ${#ref[#]} users: %s"
printf "${const_TextGreen} %s\n"
printf '%s ' "${ref[#]} "
printf "${const_TextPlain} %s\n"
printf "%s\n"
done
read -p "Now, would you like to see a list of unique users? (y/N) "
if func_YesOrNo "${REPLY}"
then
printf '%s\n' "Unique users list: "
for i in "${!NagiosUsersAllClean[#]}"
do
printf "$i: ${NagiosUsersAllClean[$i]} %s\n"
done
func_EnterToContinue
else
return 0
fi
}

Cache file content and then extract matches using regex

Please forgive a bash newbie for any silly questions.
I am really stuck here and I would love to know how this works and what I am doing wrong.
I have written this script which is supposed to capture syslog server based on protocol.
The input is as follows:
sys syslog {
include "destination remote_server {tcp(\"10.1.0.100\" port (514));tcp(\"192.168.1.5\" port (514));udp(\"192.168.1.60\" port (514));};filter f_alllogs {level (debug...emerg);};log {source(local);filter(f_alllogs);destination(remote_server);};"
remote-servers {
mysyslog {
host 192.168.1.1
}
remotesyslog1 {
host 192.168.1.2
}
remotesyslog2 {
host 192.168.1.3
local-ip 10.0.0.50
}
}
}
From this I would like to get something like in the end:
tcp=10.1.0.100
tcp=192.168.1.50
udp=192.168.1.60
udp=192.168.1.1
udp=192.168.1.2
udp=192.168.1.3
So I started with a bash script to parse the output.
#!/bin/bash
#Save output to file
syslogoutput=$(< /home/patrik/input)
echo "Testing variable:"
echo $syslogoutput
echo ""
#Declare array
tcpservers=()
echo $syslogoutput | while read line ; do
matches=($(echo $line | grep -Po '(tcp\("[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}")'))
#If number of matches is greater than 0, save them to tcpservers
if [ ${#matches[#]} -gt 0 ]; then
tcpservers=("${matches[#]}")
#Echoing matches
echo "Testing matches in loop:"
for i in "${matches[#]}"; do
echo $i
done
fi
done
echo "Testing output:"
for i in "${tcpservers[#]}"; do
echo $i
done
I expected something like this:
...input file separated by line breaks
Testing matches in loop:
tcp("10.1.0.100"
tcp("192.168.1.5"
Testing output:
tcp("10.1.0.100"
tcp("192.168.1.5"
But instead I get:
sys syslog { include "destination remote_server {tcp(\"10.1.0.100\" port (514));tcp(\"192.168.1.5\" port (514));udp(\"192.168.1.60\" port (514));};filter f_alllogs {level (debug...emerg);};log {source(local);filter(f_alllogs);destination(remote_server);};" remote-servers { mysyslog { host 192.168.1.1 } remotesyslog1 { host 192.168.1.2 } remotesyslog2 { host 192.168.1.3 local-ip 10.0.0.50 } } }
Testing matches in loop:
tcp("10.1.0.100"
tcp("192.168.1.5"
Testing output:
So on to my questions:
Why isn't tcpservers=("${matches[#]}") working?
Why isn't the output cached with line breaks?
Why does bash scripting make me want to jump from a tall building every time I try it?
/Patrik
Don't use redirection, as it starts the loop in a subshell, and variables form a subshell don't propagate into the parent shell.
while read line ; do
# ...
done <<< "$syslogoutput"
You also overwrite the tcpservers on each iteration. Change the assignment to
tcpservers+=("${matches[#]}")
# ^
# |
# add to an array, don't overwrite

read multiple lines from file using csh scripting

hi everyone want to write scripting(ksh) to read line by line from file which contains member data and needs to be send an email consecutively per line to same email address
This is how far I got:
js020:
setenv JS "js020"
jsbeg_msg.csh
# Input Files
setenv EMAIL_MSG_FILENAME "`gdg $DATADIR/abcemail -c`"
# Apply Overrides
source $SRCDIR/override.src
#Execute program
setenv SUBJECT " Recovery Notice"
for line in $(cat $EMAIL_MSG_FILENAME)
do
echo "$line"
echo "SENDING E-MAIL MESSAGE TO xxxx"
execpgm.csh '/usr/ucb/mail -s "$SUBJECT" xyz#abc.com < "$EMAIL_MSG_FILENAME"'
if ($status != 0) then
echo "Sending e-mail in Step " ${JS} " FAILED! "
exit (-1)
endif
done
# END OF JOB
eoj_msg.csh $0
exit(0)
foreach Lineread ("cat $File_name")
setenv myline "$Lineread"
execpgm.csh 'echo "$myline" | /usr/bin/mailx -s "Notice" xyz#abc.com'
if ($status != 0) then
echo "Sending e-mail in Step " ${JS} " FAILED! "
exit (-1)
endif
end

getting error while looping through array in shell

I have written a script to ftp a set of files from my directory , but i am getting an error while trying to loop through an array.
#!/usr/bin/ksh
HOST='xxx.xxx.xxx.xxx'
USER='avio'
PASSWD='jun'
FILES[0]=D141203.T024413
FILES[1]=D150101.T012755
FILES[2]=D141203.T024418
echo 'no of files: ' ${#FILES[#]}
ftp -n -v $HOST << EOS
ascii
user $USER $PASSWD
for i in "${FILES[#]}"
do
get $i
done
bye
EOS
here is my o/p :
no of files: 3
ftp.sh[10]: i: 0403-009 The specified number is not valid for this command.
The for loop is not executed as a bash command; rather, it is passed as a string to the ftp command. Instead, use a pipe to feed the output of the command to ftp:
#!/usr/bin/ksh
HOST='xxx.xxx.xxx.xxx'
USER='avio'
PASSWD='jun'
FILES[0]=D141203.T024413
FILES[1]=D150101.T012755
FILES[2]=D141203.T024418
echo 'no of files: ' ${#FILES[#]}
{ echo "ascii"
echo "user $USER $PASSWD"
for i in "${FILES[#]}"; do
echo "get $i"
done
echo "bye"
} | ftp -n -v $HOST

bash string to array with spaces and extra delimiters

I'm trying to create arrays from strings that have pipe ("|") as delimiters and include spaces. I've been looking around for a while and I've gotten close thanks to sources like How do I split a string on a delimiter in Bash?, Splitting string into array and a bunch more. I'm close but it's not quite working. The two main problems are that there are spaces in the strings, there are starting and ending delimiters, and some of the fields are blank. Also, instead of just echoing the values, I need to assign them to variables.
Here's the format of the source data:
|username|full name|phone1|phone2|date added|servers|comments|
Example:
|jdoe | John Doe| 555-1212 | |1/1/11 | workstation1, server1 | added by me |
Here's what I need:
Username: jdoe
Fullname: John Doe
Phone1: 555-1212
Phone2:
Date_added: 1/1/11
Servers: workstation1, server1
Comments: guest account
Edit: I use sed to strip out the first and last delimiter and spaces before and after each delimiter, input is now:
jdoe|John Doe|555-1212||1/1/11|workstation1, server1|added by me
Here's things I've tried:
oIFS="$IFS"; IFS='|'
for line in `cat $userList`; do
arr=("$line")
echo "Username: ${arr[0]}" #not assigning a variable, just testing the output
echo "Full Name: ${arr[1]}"
echo "Phone 1: ${arr[2]}"
echo "Phone 2: ${arr[3]}"
# etc..
done
IFS="$oIFS"
Output:
Username:
Full Name:
Phone 1:
Phone 2:
Username: jdoe
Full Name:
Phone 1:
Phone 2:
Username: John Doe
Full Name:
Phone 1:
Phone 2:
Another thing I tried:
for line in `cat $userList`; do
arr=(${line//|/ })
echo "Username: ${arr[0]}"
echo "Full Name: ${arr[1]}"
echo "Phone 1: ${arr[2]}"
echo "Phone 2: ${arr[3]}"
# etc
done
Output:
Username: jdoe
Full Name: John
Phone 1:
Phone 2:
Username: Doe
Full Name: 555-1212
Phone 1:
Phone 2:
Any suggestions? Thanks!!
Your first attempt is pretty close. The main problems are these:
for line in `cat $userList` splits the file by $IFS, not by line-breaks. So you should set IFS=$'\n' before the loop, and IFS='|' inside the loop. (By the way, it's worth noting that the for ... in `cat ...` approach reads out the entire file and then splits it up, so this isn't the best approach if the file can be big. A read-based approach would be better in that case.)
arr=("$line"), by wrapping $line in double-quotes, prevents word-splitting, and therefore renders $IFS irrelevant. It should just be arr=($line).
Since $line has a leading pipe, you either need to strip it off before you get to arr=($line) (by writing something like $line="${line#|}"), or else you need to treat arr as a 1-based array (since ${arr[0]}, the part before the first pipe, will be empty).
Putting it together, you get something like this:
oIFS="$IFS"
IFS=$'\n'
for line in `cat $userList`; do
IFS='|'
arr=($line)
echo "Username: ${arr[1]}" #not assigning a variable, just testing the output
echo "Full Name: ${arr[2]}"
echo "Phone 1: ${arr[3]}"
echo "Phone 2: ${arr[4]}"
# etc..
done
IFS="$oIFS"
(Note: I didn't worry about the fields' leading and trailing spaces, because of the "I can do that step separately" part . . . or did I misunderstand that? Do you need help with that part as well?)
IFS='|'
while read username fullname phone1 phone2 dateadded servers comments; do
printf 'username: %s\n' "$username"
printf 'fullname: %s\n' "$fullname"
printf 'phone1: %s\n' "$phone1"
printf 'phone2: %s\n' "$phone2"
printf 'date added: %s\n' "$dateadded"
printf 'servers: %s\n' "$servers"
printf 'comments: %s\n' "$comments"
done < infile.txt
Another solution:
shopt -s extglob
infile='user.lst'
declare -a label=( "" "Username" "Full Name" "Phone 1" "Phone 2" )
while IFS='|' read -a fld ; do
for (( n=1; n<${#label[#]}; n+=1 )); do
item=${fld[n]}
item=${item##+([[:space:]])}
echo "${label[n]}: ${item%%+([[:space:]])}"
done
done < "$infile"
Leading and trailing blanks will be removed.
Using arrays and paste. Doesn't account for empty fields since OP said it's not a requirement.
userList='jdoe|John Doe|555-1212||1/1/11|workstation1, server1|added by me'
fields=("Username: " "Full Name: " "Phone 1: " "Phone 2: " "Date_added: " "Servers: " "Comments: ")
IFS='|' read -ra data <<<${userList}
paste <(IFS=$'\n'; echo "${fields[*]}") <(IFS=$'\n'; echo "${data[*]}")
Username: jdoe
Full Name: John Doe
Phone 1: 555-1212
Phone 2:
Date_added: 1/1/11
Servers: workstation1, server1
Comments: added by me
Use column if available to you.
readarray -t my_vals <<< $(seq 5)
echo "${my_vals[#]}" #1 2 3 4 5
column -to: <<< "${my_vals[#]}" #1:2:3:4:5
-t = Table Output
-o = Output Delimiter (set to ':' here)

Resources