Here is my situation. I have an array of Device id's. I know which device id's go with which ip addresses. I want to use the device id's to find the correct ip address and pass both of them in a command. I am using a shell script to do this.
Here is some psuedo code for what I want.
This is my dictionary type thing matching id's with ip's, no particular order
dictionary_thing = ( id:ip
id:ip
id:ip
id:ip )
This is my array of id that I am using in no particular order
array_of_used_ids = ( id
id
id )
Maybe make an array of objects with two properties (id and ip) for each id found
array_object
for(int i=0; i<count; i++)
{
array_object[i]= new object and set id to dictionary_thing id and ip
}
then I want to run all the commands in a for loop (or whatever you suggest)
for id in array_of_used_ids
./run_this_command with
for(int i=0; i<count; i++)
{
./command array_object[i].ip array_object[i].id
}
You already have your primary arrays built, so build a regex from the used IDs
regex="^${array_of_used_ids[0]}:"
i=1
while [ $i -lt ${#array_of_used_ids[*]} ]; do
regex="$regex|^${array_of_used_ids[$i]}:"
i=$(( $i + 1))
done
This should give you a regex like "^id:|^id:|^id:"
Then iterate your dictionary checking each against the regex, when matched, replace the separating ':' with a space
array_object=()
i=0
j=0
while [ $i -lt ${#dictionary_thing[*]} ]; do
if [[ $dictionary_thing[$i] =~ $regex ]]; then
array_object[$j] = ${dictionary_thing[$i]/:/ } #if it is possible for an ID to contain a ':', you will need a different method for splitting here
j=$(( $j + 1 ))
fi
i=$(( $i + 1 ))
done
Finally, iterate your result object and execute the command
i=0
while [ $i -lt ${#array_object[*]} ]; do
command_to_execute ${array_object[$i]}
i=$(( $i + 1 ))
done
Related
Like the title implies, I'm currently having trouble debugging a .bash file. As you can tell, I'm still very new to bash and unix in general. Inside The inputfile that hw3.bash executes on contains the following strings in two columns:
jack 80
mary 95
michael 60
jeffrey 90
The file will print out the names only, then scores only, then highest score, then lowest, then rank, etc.
Both lines 72 and 150 are the hiccups in the program that i cannot seem to debug. Is this a indentation issue or simply a grammatical issue.
line 72: unexpected EOF while looking for matching `''
line 150: syntax error: unexpected end of file
#1.The first positional argument ($1) provides the file name. Assign it to a
# variable named inputfile.
inputfile=$1
#2.Test if the variable inputfile refers to a file. If not, print out a proper
# message and exit with an exit code of 1.
if [[ -e inputfile ]]; then
echo "inputfile is not a file'
exit 1
fi
#3.Suppose each line of the input file is made up of the first name of a
#student followed by a score (the first name and score are separate by a
# space). Put the first names in the input file into an array called names.
record=( $(cat $inputfile | awk 'END{print NR}' ))
names=( $(cat $inputfile | awk'{print $1}'))
echo ${names[*]}
#4.Using a similar approach, put the corresponding scores into an
# array called scores.
scores=( $(cat $inputfile | awk'{print $2}'))
echo ${scores[*]}
#5.Print names and corresponding scores (i.e. output should look the same as
# the input file). You must use a loop to do this.
names=( $(cat $inputfile | awk'{print $1}'))
echo${names[*]}
scores=( $(cat $inputfile | awk'{print $2}'))
echo${names[*]}
for((i=0;i<${names[*] && $i<scores[*];i++))
do
echo{$names[i]} {$scores[*]}
done
#6.Find the highest scorer, and print the name and the score. You may assume
#that there is only one highest score.
maxVal=1
maxIndex=1
for(( i=0; i<$#names[*]} && i<${#scores[*]}; i++ ))
do
if [[ (-n ${scores[$i]}) ]]
then
if(( ${scores[$i]} > $maxValue ))
then
maxVal=${scores[$i]}
maxIndex=$i
fi
fi
done
echo "Highest Scorer:" ${names[$maxIndex]} " " $maxVal
#7.Find the lowest scorer, and print the name and the score. You may assume
#that there is only one lowest score.
minVal=10000
maxIndex=1
for(( i=0; i<${#names[*]} && i<${#scores[*]}; i++ ))
do
if [[ (-n ${scores[$i]}) ]]
then
if(( $minVal > ${scores[i]} ))
then
minVal=${scores[$i]}
minIndex=$i;
fi
done
echo "Lower Scorer:' ${names[$minIndex]} " " $minVal
#8.Calculate the average of all scores, and print it.
avg=0
total=0;
for(( i=0;i<${#names[*]} && i<${$#scores[*]};i++ ))
do
if [[ (-n ${scores[$i]} ]]
then
total=$(( $total + ${scores[$i]} ))
fi
#9.Sort the arrays in terms of scores (in descending order). The final
#position of each name in the array names must match with the position of the
# corresponding score in the array scores.
m=${names[*]}
n=${scores[*]}
for(( i=0; i<$n && i<$m; i++ ))
do
maxValue=1
maxIndex=0
for(( i=0; j<$n && j<$m; j++))
do
if [[ !(-z ${scores[$j]} ) ]]
then
if (( $maxVal < ${scores[$j]} ))
then
maxVal=${scores[$j]}
maxIndex=$j
fi
fi
done
a1[${#a1[*]}]=$maxVal
a2[${#a2[*]}]=${names[$maxIndex]}
unset scores[$maxIndex]
done
for(( i=0; j<${#a2[*]} && i < ${#a1[*]; i++ ))
do
echo ${a2[i]} ${a1[i]}
done
#10.Print sorted names and scores. Put the rank of each student before the
m=${#names[*]}
n=${#scores[*]}
for (( i=0; i<$n && i<$m; i++ ))
do
maxVal=1
maxIndex=0
for (( j=0: j<$n && j<$m; j++ ))
do
if [[ !_-z ${scores[$j]}) ]]
then
if (( $maxVal < ${scores[$j]}
maxIndex=$j
fi
fi
done
a1[${#a1[*]}]=$maxVal
a2[${#a2[*]}=${names[$maxIndex]}
unset scores[$maxIndex]
done
k=1
while [[ $k -lt ${#a2[*]} ]]
do
for(( i=0;i < ${#a2[*]} && i < ${#a1[*]};i++ ))
do
echo "Ranking" $k "is:" ${a2[i]}
k=$(($k+1))
done
done
Like you can see in your line, just before the then one, you mixed single and double quotes:
echo "inputfile is not a file'
Just replace the final simple quote, by a double one.
I have a while loop with an string array and a simple randomize for them.
My problem however is to count how many times the same strings have appear when the loop was running.
Ex :
oc/open string has appeared 3 times
rw/read string has appeared 2 times
oc/close string has appeared 3 times
etc....
At the moment im using if else methods inside the loop, but there must be a better way to count them? Any tips?
function injection {
COUNTER=0
countopen=0
while [ $COUNTER -lt 10 ]; do
module[0]="oc/open"
module[1]="oc/close"
module[2]="rw/read"
module[3]="rw/write"
randModule=$[$RANDOM % ${#module[#]}]
export MODULE=${module[$randModule]}
echo $MODULE
if [ $randModule == 0 ]; then
let countopen++
#let countclose++
#etc
#etc
fi
let COUNTER++
done
echo "Open $countopen"
}
injection
If you can use external commands, use sort and uniq to count the occurrences:
#!/bin/bash
module=( oc/open oc/close rw/read rw/write )
for i in {1..100} ; do
echo ${module[RANDOM % ${#module[#]}]}
done | sort | uniq -c
You can also count them yourself in an associative array:
#!/bin/bash
module=( oc/open oc/close rw/read rw/write )
declare -A count
for i in {1..100} ; do
mod=${module[RANDOM % ${#module[#]}]}
(( ++count[$mod] ))
done
for m in "${module[#]}" ; do
printf '%s %d\n' "$m" "${count[$m]}"
done
I'm trying to learn how to use arrays in bash. I'm writing a script that asks the user for three numbers and figures out what the biggest number is. I want the script to force the user to enter only numeric values. Furthermore, I want the three numbers should be different. I'm using an array to store user input. This is what I have so far:
## declare variables
order=("first" "second" "third")
answers=()
RED='\033[0;31m'
NC='\033[0m' # No Color
numbersonly() {
if [[ ! $1 =~ ^[0-9]+$ ]]; then
echo "${RED}$1 is not a valid number.${NC}"
else
answers+=("$input")
break
fi
}
inarray(){
for e in ${answers[#]}; do
if [[ $1 == $e ]]; then
echo "${RED}Warning.${NC}"
fi
done
}
readnumber(){
for i in {1..3}; do
j=$(awk "BEGIN { print $i-1 }")
while read -p "Enter the ${order[$j]} number: " input ; do
inarray $input
numbersonly $input
done
done
}
displayanswers(){
echo "Your numbers are: ${answers[#]}"
}
biggestnumber(){
if (( ${answers[0]} >= ${answers[1]} )); then
biggest=${answers[0]}
else
biggest=${answers[1]}
fi
if (( $biggest <= ${answers[2]} )); then
biggest=${answers[2]}
fi
echo "The biggest number is: $biggest"
}
main(){
readnumber
displayanswers
biggestnumber
}
main
Right now, I can get the script to display a warning when the user enters a number that was previously entered, but I can't seem to find the proper syntax to stay in the while loop if the user input has already been entered. Thoughts?
I found a way around it. My problem was twofold: 1) I didn't realize that if you have a for loop nested in a while loop, you'll need two break statements to exit the while loop; 2) having two functions within the same while loop made it hard to control what was happening. By merging inarray() and numbersonly() into a new function, I solved the double conditional issue. The new function looks like this:
testing(){
for item in ${answers[*]}
do
test "$item" == "$1" && { inlist="yes"; break; } || inlist="no"
done
if [[ $inlist == "yes" ]]; then
echo "${RED}$1 is already in list.${NC}"
else
if [[ ! $1 =~ ^[0-9]+$ ]]; then
echo "${RED}$1 is not a valid number.${NC}"
else
answers+=("$input")
break
fi
fi
}
Without much study here is what leapt off the screen to me follows. Beware I haven't actually tested it... debugging is an exercise left to the student.
Recommend using newer "function" definitions as you can declare local variables. () definitions do not allow localized variables.
function inarray
{
local e; #don't muck up any variable e in caller
...
}
To calculate values avoid extra awk and use j=$(( i - 1 ));
biggestnumber should likely use a loop.
Overall comment:
nummax=3; #maximum value defined in just one place
# loop this way... showing optional {} trick for marking larger loops
for (( n = 0; n < nummax; ++n )); do
{
nx=$(( 1 + n )); #1 based index
} done;
Hint: should stop input loop once all input present. Could also add:
if [ "" == "${input:-}" ]; then break;
for (( a = 0; a < ${#answer[*]}; ++a )); do
Note the extensive use of double quotes to avoid syntax errors if the variable value is empty or contains many shell metacharacters, like spaces. I can't tell you how many bug reports I've fixed by adding the quotes to existing code.
[[ ... ]] expressions use file name tests, not regular expressions. The closest you can get to [[ ! $1 =~ ^[0-9]+$ ]]; is using [[ "$1" != [0-9]* ]] && [[ "$1" != *[^0-9]* ]].
But I suspect ! expr >/dev/null "$i" : '[0-9][0-9]*$'; is more what you want as "expr" does use regular expressions. Don't enclose in []s. Used [0-9][0-9]* rather than [0-9]+ as "+" has given me mixed successes across all dialects of UNIX regular expressions.
This program is supposed to call the first function, read-series and then pass the input of every iteration of the while loop to the even-odds function which would tell if the number was even or odd and make VARSUMODDS=the value of VARSUMODDS+input if it was odd or make VARPRODUCTEVENS=the value of VARSUMEVENS*input. Then it would print them out. I'm sure there are a thousand syntax errors here, so please, be brutal. Keep in mind that I just started learning this language and I just came to it knowing only C++ and Java a few days ago, so don't expect me to understand complex answers. Thanks!
#! /bin/bash
TMPDIR=${HOME}/tmpdir
echo "Enter an integer: "
VARSUMODDS=0
VARPRODUCTEVENS=0
function read-series() {
while read numbers ; do
echo "Enter an integer: "
even-odds $numbers
done
echo numbers > $TMPDIR/$$.temp
return 0;
}
function even-odds() {
evenp=$(( $1 % 2 ))
if [ $evenp -eq 0 ] ; then
$VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
return 0;
else
$VARSUMODDS=$(($VARSUMODDS + $1))
return 1;
fi
}
function reduce () {
echo -n "Sum of odds: "
echo VARSUMODDS
echo -n "Product of evens: "
echo VARPRODUCTEVENS
return 0;
}
read-series
#! /bin/bash
tmpdir=${HOME}/tmpdir
mkdir -p $tmpdir
odd_sum=0
even_product=1
numbers=()
read-series() {
while read -p "Enter an integer (q to quit): " number ; do
[[ $number == [Qq]* ]] && break
even-odds $number
numbers+=($number)
done
printf "%d\n" "${numbers[#]}" > $tmpdir/$$.temp
}
even-odds() {
if (( $1 % 2 == 0 )) ; then
(( even_product *= $1 ))
else
(( odd_sum += $1 ))
fi
}
reduce () {
echo "Sum of odds: $odd_sum"
echo "Product of evens: $even_product"
}
read-series
reduce
Notes:
make sure your tmpdir exists
no good seeding your product variable with 0
use an array to store the list of numbers
provide a way to break out of the input loop
in bash, the == operator within [[ ... ]] is a pattern matching operator.
you don't actually use your function return values anywhere, so I removed them
to declare a function, you don't need to use both the "function" keyword and the parentheses
use read -p to provide the prompt
use arithmetic expressions more widely
to assign a variable, don't use $ on the left-hand side.
to get the value, you must use $
let the system use upper-case variable names, you don't want to accidentally overwrite PATH for example.
avoid writing temp files unless you really need them (for logging or auditing)
printf re-uses the format string until all the arguments are consumed. this is very handy for printing the contents of an array.
semi-colons are optional
You should initialize VARPRODUCTEVENS to 1, because multiplying anything by 0 produces 0.
$ should not be put before the variable being assigned in an assignment statement.
You can use the -p option to read to specify a prompt
You're writing to $$.temp after the loop is done. numbers will be empty then, so you're not writing anything to the file. If you want to record all the numbers that were entered, you must do that inside the loop, and use >> to append to the file instead of overwriting it.
There's no reason to use return in your functions -- nothing tests the exit status. And non-zero is usually used to mean there was an error.
You defined a function reduce to print the results, but never called it
You need to put $ before the variable names on all your echo lines.
Don't put function before function definitions; it's allowed, but not required, and not portable (it's a bash extension).
#! /bin/bash
TMPDIR=${HOME}/tmpdir
VARSUMODDS=0
VARPRODUCTEVENS=1
read-series() {
while read -p "Enter an integer: " numbers ; do
even-odds $numbers
echo $numbers >> $TMPDIR/$$.temp
done
}
even-odds() {
evenp=$(( $1 % 2 ))
if [ $evenp -eq 0 ] ; then
VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
else
VARSUMODDS=$(($VARSUMODDS + $1))
fi
}
reduce () {
echo ''
echo -n "Sum of odds: "
echo $VARSUMODDS
echo -n "Product of evens: "
echo $VARPRODUCTEVENS
}
read-series
reduce
If you run this script stand-alone, you have to end your input with CTRL-d. Here are the problems:
VARPRODUCTEVENS=0
has to be
VARPRODUCTEVENS=1
or your product will always be zero.
echo numbers > $TMPDIR/$$.temp
Seems to have no useful purpose. You are putting the string "numbers" into the file. If you use $numbers it still appears to have no purpose. You would be putting the single last number from the read into the file. From the use "number" may be a better name than "numbers"
$VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
and
$VARSUMODDS=$(($VARSUMODDS + $1))
has to be
VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
and
VARSUMODDS=$(($VARSUMODDS + $1))
Having $VARSUMODDS on the left of the assignment will try to assign to the variable named "1" (the value of $VARSUMODDS).
There is no call to reduce, so you see no results. I assume you want that at the end.
Your return statements are unnecessary, and probably not doing what you intended. You are basically setting the exit status, and non-zero implies failure.
I've been tasked with re-writing this in bash. But whilst most powershell is easy to read, i just dont get what this block is actually doing!!? Any ideas?
It takes a file which is sorted on a key first, maybe thats relevant!
Thanks for any insights!
foreach ($line in $sfile)
{
$row = $line.split('|');
if (-not $ops[$row[1]]) {
$ops[$row[1]] = 0;
}
if ($row[4] -eq '0') {
$ops[$row[1]]++;
}
if ($row[4] -eq '1') {
$ops[$row[1]]--;
}
#write-host $line $ops[$row[1]];
$prevrow = $row;
}
Perhaps a little refactoring would help:
foreach ($line in $sfile)
{
# $row is an array of fields on this line that were separated by '|'
$row = $line.split('|');
$key = $row[1]
$interestingCol = $row[4]
# Initialize $ops entry for key if it doesn't
# exist (or if key does exist and the value is 0, $null or $false)
if (-not $ops[$key]) {
$ops[$key] = 0;
}
if ($interestingCol -eq '0') {
$ops[$key]++;
}
elseif ($interestingCol -eq '1') {
$ops[$key]--;
}
#write-host $line $ops[$key];
# This appears to be dead code - unless it is used later
$prevrow = $row;
}
You are splitting a line on the '|' charater to an array row. It looks like you are using the $row array as some type of key into the $ops var. The first if test to see if the object exists if it doesn't it creates it in $ops the second and third ifs test to see if the 5th element in $row are zero and one and either increment or decrement the value created in the first if.
Approximately:
#!/bin/bash
saveIFS=$IFS
while read -r line
do
IFS='|'
row=($line)
# I don't know whether this is intended to test for existence or a boolean value
if [[ ! ${ops[${row[1]}] ]]
then
ops[${row[1]}]=0
fi
if (( ${row[4]} == 0 ))
then
(( ops[${row[1]}]++ ))
fi
if (( ${row[4]} == 1 ))
then
(( ops[${row[1]}]-- ))
fi
# commented out
# echo "$line ${ops[${row[1]}]}
prevrow=$row
done < "$sfile"