Two-level 'for' loop of a customised list - shell

Using a two-level for loop and seq works fine, and the code
for i in `seq 0 3`; do for j in `seq 0 3`; do echo $i $j; done; done
gives the expected output:
0 0
0 1
1 0
1 1
But if I want a more customised list of numbers:
for i in '-1 4.5'; do for j in '0 -2.2'; do echo $i $j; done; done
I get the output
-1 4.5 0 -2.2
Is there an easy way to do this?

The usage of single ticks is preventing the shell from tokenizing the list supplied to the for loop:
#!/bin/sh
for i in -1 4.5
do for j in 0 -2.2
do # -- keeps the highly portable printf utility from
# interpreting '-' as an argument
printf -- "$i" "$j\n"
done
done

var a = [1,2,3];
var b = [1,2,3];
for (i in a) {
for (j in b) {
alert (i + j);
}
}
I have also created a jsfiddle:
http://jsfiddle.net/f8k5tpqm/1/

Related

Bash continue [n] ignored in piped to while read loops. for loop behaves as expected

In a script I'm writing, I try to exit out of the second nested while loop, and the continue statement appears to be simply ignored, and I'm unsure of exactly why.
From the manual:
continue [n]:
Resume the next iteration of the enclosing for, while, until, or select loop. If n is specified,
resume at the nth enclosing loop. n must be ≥ 1. If n is greater than the number of enclosing
loops, the last enclosing loop (the ``top-level'' loop) is resumed. The return value is 0 unless n
is not greater than or equal to 1.
A minimal example that illustrates my problem (ex1.sh):
#!/bin/bash
printf "%d\n" {1..3} | while read i; do
printf "%d\n" {1..3} | while read j; do
sum=$(expr $i + $j)
echo "$i + $j = $sum"
[ $sum -ge 5 ] && echo continue && continue 2
done
echo 'should only see this for first cycle of i (2+3 >= 5)'
done
Output of ex1.sh:
1 + 1 = 2
1 + 2 = 3
1 + 3 = 4
should only see this for first cycle of i (2+3 >= 5)
2 + 1 = 3
2 + 2 = 4
2 + 3 = 5
continue
should only see this for first cycle of i (2+3 >= 5)
3 + 1 = 4
3 + 2 = 5
continue
3 + 3 = 6
continue
should only see this for first cycle of i (2+3 >= 5)
Why is the continue 2 ignored? It clearly echos out "continue", and the fact that continue runs is confirmed by running a trace with set -x
The use of printf is obviously silly ex1.sh, but reproduces the issue I'm having with getting continue to work in a while read x... loop.
A for loop works as expected (ex2.sh):
#!/bin/bash
for i in {1..3}; do
for j in {1..3}; do
sum=$(expr $i + $j)
echo "$i + $j = $sum"
[ $sum -ge 5 ] && echo continue && continue 2
done
echo 'should only see this for first cycle of i (2+3 >= 5)'
done
Output of ex2.sh:
1 + 1 = 2
1 + 2 = 3
1 + 3 = 4
should only see this for first cycle of i (2+3 >= 5)
2 + 1 = 3
2 + 2 = 4
2 + 3 = 5
continue
3 + 1 = 4
3 + 2 = 5
continue
So, why does ex1.sh seem to ignore the continue 2 builtin, whereas ex2.sh behaves as expected?
Fixed the first example myself thanks to M. Nejat Aydin:
#!/bin/bash
while read i; do
while read j; do
sum=$(expr $i + $j)
echo "$i + $j = $sum"
[ $sum -ge 5 ] && echo continue && continue 2
done < <(printf "%d\n" {1..3})
echo 'should only see this for first cycle of i (2+3 >= 5)'
done < <(printf "%d\n" {1..3})
The issue is that pipelines create subshells, so my continue statement was behaving as expected according to the manual, continuing at the outermost loop that subshell was aware of.
Your while loops are not nested. They're running in different subshells. continue cannot continue from the parent shell. A pipe (|) creates a subshell. On the other hand, the version below should work as expected because both whiles are running in the same shell:
while read i; do
while read j; do
sum=$(expr $i + $j)
echo "$i + $j = $sum"
[ $sum -ge 5 ] && echo continue && continue 2
done < <(printf "%d\n" {1..3})
echo 'should only see this for first cycle of i (2+3 >= 5)'
done < <(printf "%d\n" {1..3})

Loop BASH with counter in one line

I wait program finish and print nothing; but this intro in loop:
while [ 0 > 0 ]; do echo 1; done
logically 0 no't is > to 0...
why get loop?
How I can get nothing by screen? and program finish fine?
After of my progam "nothing to do":
while [ 0 > 0 ]; do echo 1; done
I like in one line:
q = 0; while [ q < 9 ]; q ++; do echo q; done
it´s possible in one line?
Thanks
q = 0; while [ q < 9 ]; q ++; do echo q; done
Just 5 errors in one line.
q=0 may not have white space around the assignment operator
q ++ has to be in double round parens
q < 9 has to be -lt (less than)
in single brackets [ $q -lt 9 ] q needs to be $q
the do should follow the while
Possible solution:
q=0; while [[ q -lt 3 ]]; do ((q++)); echo $q; done
1
2
3
echoing values can be done with
echo {1..9}
too, but is not flexible, so you can't use variable expansion inside, like echo {1..$n}. The canonical way of doing initialization, increment and threshold check, is a for loop:
for (( q=1; q < 4; ++q)); do echo $q ; done
There is the external program seq, which is not so much recommended, for that reason:
seq 1 3
First question:
while [ 0 > 0 ]; do echo 1; done
Look for a file 0 where you used file redirection (instead of -gt), like in echo foo > 0.file.
Instead of
while [ 0 -gt 0 ]; do echo 1; done
because it does nothing. It doesn't wait for anything. Either your program is sequential, then it is finished at that point anyhow. Or there is a program/command in the background running, which doesn't care about this anti loop anyhow.
For your second question, use
for (( expr1 ; expr2 ; expr3 )) ; do list ; done
Also refer to man bash

get column numbers that are equal to X

imagine that I've the following string:
1 0 1 1 1
a simple implementation to get the column numbers that are equal to "1" is:
for column_number in $(seq 1 5); do
zero_or_one=$(echo "1 0 1 1 1" | cut -d' ' -f$column_number)
if [ "$zero_or_one" -eq "1" ]; then
echo "$column_number"
fi
done
however, as my strings tend to be very long, that loop takes ages (~ 1min).
is there any other way using for example awk, sed, ..., to get the column numbers that are equal to "1" or "0" ?
expected output if I'm looking for "1":
1
3
4
5
expected output if I'm looking for "0":
2
It's not clear from your question but this MAY be what you want:
$ awk -v RS=' ' '$0{print NR}' <<<'1 0 1 1 1'
1
3
4
5
$ awk -v RS=' ' '!$0{print NR}' <<<'1 0 1 1 1'
2
This looks like the kind of thing that you should awk for:
awk '{ for (i = 1; i <= NF; ++i) if ($i == 1) print i }' <<<'1 0 1 1 1'
Loop through the fields, compare their value and print the ones that match.
That said, it's worth mentioning that you could improve the performance of your existing approach too:
while read -ra cols; do
for (( i = 1; i <= ${#cols[#]}; ++i )); do
[[ ${cols[i-1]} -eq 1 ]] && echo "$i"
done
done <<<'1 0 1 1 1'
This uses native shell commands rather than executing separate processes to obtain each value, so it will be much quicker than your loop.
Note that the array is zero-indexed, so I've used ${cols[i-1]} in order to obtain the same output.

Bash Script accepting a number, then printing a set of int from 0 to the number entered

I am trying to write a bash script that accepts a number from the keyboard, and then prints a set of integers from 0 to the number entered. I can't figure out how to do it at all.
This is my code:
while [ 1 ]
do
echo -n "Enter a color: "
read user_answer
for (( $user_answer = $user_answer; $user_answer>0; $user_answer--))
echo $user_answer
fi
done
exit
The error I'm recieving is:
number_loop: line 10: syntax error near unexpected token echo'
number_loop: line 10: echo $user_answer'
Assign a separate variable in order to use increment/decrement operators. $user_answer=$user_answer will always be true and it will throw an error when trying to use decrement. Try the following :
#!/bin/bash
while [ 1 ]
do
echo -n "Enter a color: "
read user_answer
for (( i=$user_answer; i>0; i-- ))
do
echo $i
done
done
exit
You missed the do statement between your for and the echo.
bash has many options to write numbers. What you seem to be trying to do is easiest done with seq:
seq $user_answer -1 0
If you want to use your loop, you have to insert a ; do and replace the fi with done, and replace several $user_answer:
for (( i = $user_answer; i>0; i--)); do
echo $i
done
(btw: I assumed that you wanted to write the numbers in reverse order, as you are going backwards in your loop. Forwards is even easier with seq:
seq 0 $user_input
)
This is where a c-style loop works particularly well:
#!/bin/bash
for ((i = 1; i <= $1; i++)); do
printf "%s\n" "$i"
done
exit 0
Example
$ bash simplefor.sh 10
1
2
3
4
5
6
7
8
9
10
Note: <= is used as the for loop test so it 10 it iterates 1-10 instead of 0-9.
In your particular case, iterating from $user_answer you would want:
for (( i = $user_answer; i > 0; i--)); do
echo $i
done
The for loop is a bash internal command, so it doesn't fork a new process.
The seq command has a nice, one-line syntax.
To get the best of the twos, you can use the { .. } syntax:
eval echo {1..$answer}

bash script loop multiple variables

I am trying to write something like following
for i in {a..z} && j in {1..26}
do
echo "/dev/sd"$i"1 /disk$j ext4 noatime 1 1" >> test
done
Of course this is not correct syntax. Can someone please help me with correct syntax for doing this?
To be generic, you can use 'length' as shown below.
#!/bin/bash
# Define the arrays
array1=("a" "b" "c" "d")
array2=("w" "x" "y" "z")
# get the length of the arrays
length=${#array1[#]}
# do the loop
for ((i=0;i<=$length;i++)); do
echo -e "${array1[$i]} : ${array2[$i]}"
done
You can also assign the array like the following
array1=`awk -F" " '$1 == "CLIENT" { print $2 }' clientserver.lst`
You can use arrays for that:
A=({a..z}) B=({1..26})
for (( I = 0; I < 26; ++I )); do
echo "/dev/sd${A[I]} /disk${B[I]} ext4 noatime 1 1" >> test
done
Example output:
/dev/sda /disk1 ext4 noatime 1 1
...
/dev/sdz /disk26 ext4 noatime 1 1
Update:
As suggested you could just use the index for values of B:
A=('' {a..z})
for (( I = 1; I <= 26; ++I )); do
echo "/dev/sd${A[I]} /disk${I} ext4 noatime 1 1" >> test
done
Also you could do some formatting with printf to get a better output, and cleaner code:
A=('' {a..z})
for (( I = 1; I <= 26; ++I )); do
printf "%s%20s%15s%15s%4s%2s\n" "/dev/sd${A[I]}" "/disk${I}" ext4 noatime 1 1 >> test
done
Also, if you don't intend to append data to file, but only write once every generated set of lines, just make redirection by block instead:
A=('' {a..z})
for (( I = 1; I <= 26; ++I )); do
printf "%s%20s%15s%15s%4s%2s\n" "/dev/sd${A[I]}" "/disk${I}" ext4 noatime 1 1
done > test

Resources