Bash While Loop Variable Mishaps - arrays

Before you get concerned, this (hopefully) isn't another of the many threads about having variable access problems from a while loop in BASH. I looked at those and implemented the redirect method, but I'm having some issues.
Code:
#!/bin/bash
rm /tmp/sched.now
rm /tmp/sched.later
NOWURL="schedNOWurl"
LATERURL="schedLATERurl"
curl -s $NOWURL --output /tmp/sched.now
curl -s $LATERURL --output /tmp/sched.later
re='^[0-9]+$'
if grep -q 'no shifts' /tmp/sched.now ; then
nowpeople[0]="No Shifts Assigned For This Time"
nowtime[0]="Now"
else
while read line
do
if [[ ${line:0:1} =~ $re ]] ; then
nowtime[$nowcount]=$line
((nowcount++))
elif [[ ${line:0:1} == '-' ]] ; then
nowpeople[$nowcount]=$line
((nowcount++))
fi
done < /tmp/sched.now
fi
declare -a latertime
declare -a laterpeople
if grep -q 'no shifts' /tmp/sched.later ; then
laterpeople[0]="No Shifts Assigned For This Time"
latertime[0]="Until 23:59"
else
while read line
do
if [[ ${line:0:1} =~ $re ]] ; then
latertime[$latercount]=$line
((latercount++))
elif [[ ${line:0:1} == '-' ]] ; then
laterpeople[$latercount]=$line
((latercount++))
fi
done < /tmp/sched.later
fi
echo ${nowpeople[#]}
echo ${nowtime[#]}
echo ${laterpeople[#]}
echo ${latertime[#]}
The URLs that I'm getting are HTML files. The text I'm attempting to pull in the loops looks something like this
11a-1:30p
- John Doe
with some HTML thrown above and below it. Note that these are all on their own lines.
The issue I'm facing is that the loops aren't saving all of the matches to the array. In the second loop, it should pull three entries into each array. Right now it pulls one with malformed text as follows:
- John Doe
1:30p-4pp
Notice the missing m in pm and the replaced m with a p in pp.
The strange thing is that if I echo $line before the array statement, the line is pulled from the file fine, so it sounds like it's something in the way I'm saving to the array.
Suggestions? Please let me know if you need more details.

Related

Fetching data into an array

I have a file like this below:
-bash-4.2$ cat a1.txt
0 10.95.187.87 5444 up 0.333333 primary 0 false 0
1 10.95.187.88 5444 up 0.333333 standby 1 true 0
2 10.95.187.89 5444 up 0.333333 standby 0 false 0
I want to fetch the data from the above file into a 2D array.
Can you please help me with a suitable way to put into an array.
Also post putting we need put a condition to check whether the value in the 4th column is UP or DOWN. If it's UP then OK, if its down then below command needs to be executed.
-bash-4.2$ pcp_attach_node -w -U pcpuser -h localhost -p 9898 0
(The value at the end is getting fetched from the 1st column.
You could try something like that:
while read -r line; do
declare -a array=( $line ) # use IFS
echo "${array[0]}"
echo "${array[1]}" # and so on
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
Or:
while read -r -a array; do
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
This works only if field are space separated (any kind of space).
You could probably mix that with regexp if you need more precise control of the format.
Firstly, I don't think you can have 2D arrays in bash. But you can however store lines into a 1-D array.
Here is a script ,parse1a.sh, to demonstrate emulation of 2D arrays for the type of data you included:
#!/bin/bash
function get_element () {
line=${ARRAY[$1]}
echo $line | awk "{print \$$(($2+1))}" #+1 since awk is one-based
}
function set_element () {
line=${ARRAY[$1]}
declare -a SUBARRAY=($line)
SUBARRAY[$(($2))]=$3
ARRAY[$1]="${SUBARRAY[#]}"
}
ARRAY=()
while IFS='' read -r line || [[ -n "$line" ]]; do
#echo $line
ARRAY+=("$line")
done < "$1"
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
for line in "${ARRAY[#]}"; do
#echo $line
if [ "$(echo $line | awk '{print $4}')" == "down" ]; then
echo "Replace this with what to do for down"
else
echo "...and any action for up - if required"
fi
done
echo "Element access of [2,3]:"
echo "get_element 2 3 : "
get_element 2 3
echo "set_element 2 3 left: "
set_element 2 3 left
echo "get_element 2 3 : "
get_element 2 3
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
It can be executed by:
./parsea1 a1.txt
Hope this is close to what you are looking for. Note that this code will loose all indenting spaces during manipulation, but a formatted update of the lines could solve that.

How to read multiple arrays in Bash and skip the array after first match

This is my bash script
I have 4 set of arrays and in each set i am ssh-ing to each server to find if /data filesystem exists. If it matches it should skip the array and move to next arry. I am unable to do with break as it exits the entire script. Any ideas ?
declare -a siteA=("server01" "server02" "server03")
declare -a siteB=("server04" "server05" "server06")
declare -a siteB=("server07" "server08" "server09")
declare -a siteB=("server10" "server11" "server12")
cmd=$(df -h|grep /data)
for i in "${siteA[#]}" "${siteB[#]}" "${siteC[#]}" "${siteD[#]}"; do
ping -c 2 ${i} > /dev/null 2>&1
if [[ $? -eq 0 ]] ; then
X=$(ssh root#${i} -q $cmd1 2>&1)
if [[ $Z == "/data" ]]; then
echo "$i: has /data"
fi
fi
done
To continue to the next array when you find a match, simply wrap your loop contents in a parameter loop within a function and call that for each site:
has_running_host() {
for host
do
[code which `break`s on a match]
done
}
has_running_host "${siteA[#]}"
has_running_host "${siteB[#]}"
[…]
Well although not very nice, you could use two loops in combination with eval:
for j in siteA siteB siteC siteD; do
for i in $(eval echo \${${j}[#]}); do
echo $i
done
done
this would then allow you to use break in the inner loop and therefore jumping to the next array
That worked for me, also getting output from a remote ssh is a challenge

echo a variable and then grep to see if value exist in a file is not returning anything. Unix Shell Scripting

I'm trying to figure out how to determine if a variable contains a value from a file using grep, this is not returning anything, so I'm going to explain it.
I have my code that is this:
MyFiles="MyFile-I-20160606_141_Employees.txt"
DirFiles="/dev/fs/C/Users/salasfri/Desktop/MyFiles.txt"
for OutFile in $(cat $DirFiles); do
if [[ $( echo $MyFiles | grep -c $OutFile ) -gt 0 ]]; then
print "The file $OutFile exist!!"
fi
done
and the file in /dev/fs/C/Users/salasfri/Desktop/MyFiles.txt contains the following values:
MyFile-I-*_141_Employees.txt
MyFile-I-*_141_Products.txt
MyFile-I-*_141_Deparments.txt
the idea is verify if the variable "MyFiles" is found in the MyFiles.txt file, as you can see is using the pattern "*" due that is a date, it will change.
that solutions is not returning any count of files, there's something that I'm doing wrong?
You can try to change the searchstring before searching.
An example with three teststrings:
for teststring in MyFile-I-20160606_141_Employees.txt MyFile-I-20160606_142_Employees.txt MyFile-I-20160606_141_Others.txt
do
grepstr=$(sed 's/[0-9]\{8\}_/*_/' <<< "${teststring}")
fgrep "${grepstr}" "${DirFiles}"
found=$(fgrep "${grepstr}" "${DirFiles}")
if [ $? -eq 0 ]; then
echo "${found} matches ${teststring}."
fi
done
In your case you can make the code shorter with
fgrep -q "$(sed 's/[0-9]\{8\}_/*_/' <<< "${MyFiles}")" $DirFiles &&
echo "The file $(sed 's/[0-9]\{8\}_/*_/' <<< "${MyFiles}") exist!!"
Your patterns are glob-style patterns, not regular expressions. The pattern abc-*_X.txt will not match the string abc-1234_X.txt.
You want to use a shell construct that does glob matching.
MyFiles="MyFile-I-20160606_141_Employees.txt"
sed 's/\r$//' "/dev/fs/C/Users/salasfri/Desktop/MyFiles.txt" \
| while IFS= read -r Pattern; do
if [[ $MyFiles == $Pattern ]]; then
print "$MyFiles matches pattern $Pattern"
break
fi
done

Array returns false when i know it should be true

I know that this particular volume should be coming out as true but it keeps returning as false. Can anyone see what is wrong with it? My guess is it has something to do with the if statement but i have no idea what. the "3D/Gather" are 2 seperate strings but i want it so that if they are together then it returns true. This is the start of what is going to be quite a long script so i want to make it a function that i can call upon later on. Thanks in advance for your help.
#!/bin/bash
session_path=$PWD/testSess/3DBPvol.proc
outPath=""
sess=$session_path
{
ThreeD="false"
while read line; do
IFS=" "
arr=$(echo ${line})
unset IFS
for i in ${arr[#]} ; do
if [[ "$i" =~ "3D" ]] && [[ "$i" =~ "Gather" ]]; then
ThreeD="true"
fi
done
done < "$sess"
echo "Is 3D? $ThreeD"
}
arr=$(echo ${line})
This doesn't create an array, you'd need extra parens:
arr=($(echo ${line}))
But you don't actually need the echo, this should be enough:
arr=(${line})
So you want to see if the file under /proc contains "3D Gather"? You can do that simply with a grep:
#!/bin/bash
session_path=$PWD/testSess/3DBPvol.proc
grep -q '3D Gather' "$session_path" && ThreeD=true || ThreeD=false
echo "Is 3D? $ThreeD"
It seems you don't need to break the line into a BASH array. Consider this rafactored script that does the same job but with a lot less code:
ThreeD="false"
while read line; do
[[ "$line" == *"3D Gather"* ]] && ThreeD="true" && break
done < "$sess"
echo "Is 3D? $ThreeD"

Is there a way to search an entire array inside of an argument?

Posted my code below, wondering if I can search one array for a match... or if theres a way I can search a unix file inside of an argument.
#!/bin/bash
# store words in file
cat $1 | ispell -l > file
# move words in file into array
array=($(< file))
# remove temp file
rm file
# move already checked words into array
checked=($(< .spelled))
# print out words & ask for corrections
for ((i=0; i<${#array[#]}; i++ ))
do
if [[ ! ${array[i]} = ${checked[#]} ]]; then
read -p "' ${array[i]} ' is mispelled. Press "Enter" to keep
this spelling, or type a correction here: " input
if [[ ! $input = "" ]]; then
correction[i]=$input
else
echo ${array[i]} >> .spelled
fi
fi
done
echo "MISPELLED: CORRECTIONS:"
for ((i=0; i<${#correction[#]}; i++ ))
do
echo ${array[i]} ${correction[i]}
done
otherwise, i would need to write a for loop to check each array indice, and then somehow make a decision statement whether to go through the loop and print/take input
The ususal shell incantation to do this is:
cat $1 | ispell -l |while read -r ln
do
read -p "$ln is misspelled. Enter correction" corrected
if [ ! x$corrected = x ] ; then
ln=$corrected
fi
echo $ln
done >correctedwords.txt
The while;do;done is kind of like a function and you can pipe data into and out of it.
P.S. I didn't test the above code so there may be syntax errors

Resources