Bash: for in loop with brace expansion in list - bash

I would like to write a for in loop in which the possible values are taken from another variable. The part that I can't figure out is how to include values that need brace expansion. For example:
TEXT="a b c d{a..c} e f"
for VAR in $TEXT; do echo $VAR; done
What i get is
a
b
c
d{a..c}
e
f
But what I want is
a
b
c
da
db
dc
e
f
Does anyone know how I can get this to work?
Thank you very much!

I fear that you may have to resort to using eval in this case...However, I'd suggest that you avoided using a loop:
eval "printf '%s\n' $TEXT"
Using eval means that the braces will be expanded, producing your desired outcome.
You can see what happens using set -x:
$ set -x
$ eval "printf '%s\n' $TEXT"
+ eval 'printf '\''%s\n'\'' a b c d{a..c} e f'
++ printf '%s\n' a b c da db dc e f
a
b
c
da
db
dc
e
f
Note that the expansion occurs before the list is passed as arguments to printf.
If you get to define $TEXT yourself and want to loop through each of its expanded elements, I'd suggest using an array instead:
text=( a b c d{a..c} e f )
for var in "${text[#]}"; do
# whatever with "$var"
done
This is the preferred solution as it doesn't involve using eval.

EDIT
While there's a time & place for eval, it's not the safest approach for the general case. See Why should eval be avoided in Bash, and what should I use instead? for some reasons why.
Use eval:
TEXT="a b c d{a..c} e f"
for VAR in $TEXT; do eval echo $VAR; done
Output:
a
b
c
da db dc
e
f
If you want to loop over every element, you'll have to nest your loops:
for VAR in $TEXT; do
for VAR2 in $(eval echo $VAR); do
echo $VAR2
done
done
Output:
a
b
c
da
db
dc
e
f
Or use the eval on TEXT rather than each element:
TEXT="$(eval echo "a b c d{a..c} e f")"
for VAR in $TEXT; do echo $VAR; done
Output:
a
b
c
da
db
dc
e
f

Without eval, but with command substitution:
$ text="$(echo a b c d{a..c} e f)"
$ echo "$text"
a b c da db dc e f
And, as mentioned in Tom's answer, an array is probably the better way to go here.

Related

Accessing variable value in BASH for loop

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
Closed 1 hour ago.
I want to assign and print variable values within a for loop in BASH.
My code looks like this:
tea=(A B C D E F G)
c=0
for (( i=1; i<${#tea[#]}; i++ ));
do
eval "var$c=${tea[$i]}";
c=$((c+1));
echo "$var$c" >> example.txt
done
The output I get in my txt file is: 1 2 3 4 5 6. The output I expect is B C D E F G. I don't understand why am I getting this output, am I not assigning values to var$c correctly or this echo command cannot read my variable value? I would appreciate your help a lot.
One approach using a nameref:
tea=(A B C D E F G)
c=0
for (( i=1; i<${#tea[#]}; i++ ));
do
declare -n varC="var$c" # nameref
varC="${tea[$i]}"
c=$((c+1))
echo "$varC" >> example.txt
done
This generates:
$ cat example.txt
B
C
D
E
F
G

bash eval equation right value can not include space

In bash shell script, I want to create series of variables and assign string values to them
a=aaa
eval $a="a b c"
turns out :
b not found...
Why the right value cannot contain space?
It's because eval $a="a b c" is expanded to aaa=a b c
Solutions:
eval "$a=\"a b c\""
# or better
printf "-v$a" "a b c"
You can reach for declare when you want to use eval for variable assignment.
$ declare "$a=a b c"
$ echo $aaa
a b c

How to pass items to bash for loop on multiple lines?

I can do this in bash:
for n in a b c d e ; do
echo $n
done
If a, b, c, d, e turn out to be long lines, without using a separate variable, how do I put them each on a separate line in the for loop syntax?
Split the line with \:
$ for i in a \
> b \
> c \
> d ; do echo $i ; done
a
b
c
d
I'd use a "here document":
while read n; do
echo $n
done <<EOF
some detailed stuff here
other things on the next line, blah blah blah
EOF
Of course in this particular example you can replace the entire while loop with cat but I suppose your real code is more involved.
you can put a b c d e in a file and run a loop over that file.
while read line
do
echo "$line \n"
done < file

while loops offset in shell script

I/P file has data as follows:
Y
REQUIRES Z
A
REQUIRES B
C
REQUIRES D
REQUIRES E
REQUIRES F
G
REQUIRES H
I
REQUIRES J
EXACT OUTPUT FILE REQUIRED:
Y REQUIRES Z
A REQUIRES B
C REQUIRES D
C REQUIRES E
C REQUIRES F
G REQUIRES H
I REQUIRES J
I am using while loops to traverse the file.
while read line
do
if (condition)
{..
}
while read anoterline
do
done
done <inputfile
The problem I am facing is that
when inner while loop traverses say 4 lines and i break the inner
loop the outer while loop's offset is set to the offset at which the
inner while has stopped.
So I am missing the 4 lined data in execution of my outer loop.
I need the outer while loop to start off from the offset at which it had stopped
.
There's no reason to use nested loops here.
item=
while read -r first second _; do
if [[ $first = REQUIRES ]]; then
printf '%s REQUIRES %s\n' "$item" "$second"
else
item=$first
fi
done <inputfile
Invocation and output demonstrated at http://ideone.com/JejDyG
You can do this using awk
$ awk 'NF==1{a=$1};NF>1{print a,$0}' file
Y REQUIRES Z
A REQUIRES B
C REQUIRES D
C REQUIRES E
C REQUIRES F
G REQUIRES H
I REQUIRES J
Something like
#!/bin/bash
while read -r line; do
[[ $line =~ ^[A-Z]$ ]] && one="$line"
[[ $line =~ ^REQUIRES(.*) ]] && echo "$one $line"
done < "file"
Example
> ./abovescript
Y REQUIRES Z
A REQUIRES B
C REQUIRES D
C REQUIRES E
C REQUIRES F
G REQUIRES H
I REQUIRES J

looping over arguments in bash

In bash, I can loop over all arguments, $#.
Is there a way to get the index of the current argument? (So that I can refer to the next one or the previous one.)
You can loop over the argument numbers, and use indirect expansion (${!argnum})to get the arguments from that:
for ((i=1; i<=$#; i++)); do
next=$((i+1))
prev=$((i-1))
echo "Arg #$i='${!i}', prev='${!prev}', next='${!next}'"
done
Note that $0 (the "previous" argument to $1) will be something like "-bash", while the "next" argument after the last will come out blank.
Not exactly as you specify, but you can iterate over the arguments a number of different ways.
For example:
while test $# -gt 0
do
echo $1
shift
done
Pretty simple to copy the positional params to an array:
$ set -- a b c d e # set some positional parameters
$ args=("$#") # copy them into an array
$ echo ${args[1]} # as we see, it's zero-based indexing
b
And, iterating:
$ for ((i=0; i<${#args[#]}; i++)); do
echo "$i ${args[i]} ${args[i-1]} ${args[i+1]}"
done
0 a e b
1 b a c
2 c b d
3 d c e
4 e d

Resources