I am writing bash script for first time . I have to use nested for loop. I have an array like this:
(foo,bar a,b)
There are two elements in the array and each element in array is separated by commas. I want each element in array to split and print each element. The final result I want is
foo
bar
a
b
I am using nested for loop as below but I am not getting any response
IFS=','
echo "class names is::${classNames[#]}" // prints foo,bar a,b
for ((n=0;n<${#classNames[#]};n++)){
classes=${classNames[n]}
for ((i=0;n<${#classes[#]};i++)){
echo "${eachClass[i]}"
}
}
You could:
arr=(foo,bar a,b)
for i in "${arr[#]}"; do
while IFS=, read -ra arr_in; do
for j in "${arr_in[#]}"; do
echo "$j"
done
done <<<"$i"
done
or like:
for i in "${arr[#]}"; do
readarray -d, -t arr_in < <(printf "%s" "$i")
for j in "${arr_in[#]}"; do
echo "$j"
done
done
or just:
while IFS=, read -ra arr_in; do
for j in "${arr_in[#]}"; do
echo "$j"
done
done < <(printf "%s\n" "${arr[#]}"
or even:
while IFS=, read -ra arr_in; do
printf "%s\n" "${arr_in[#]}"
done < <(printf "%s\n" "${arr[#]}"
but that said:
printf "%s\n" "${arr[#]}" | tr ',' '\n'
will also print the same.
Do not use { } as loop terminators - it's undocumented very old bash/shell syntax. Use do..done.
classes=... is a normal variable, not an array, and in such assignment IFS does not matter - here the right part of = is not word splitted. To assign an array, use braces classes=(${classNames[n]}).
You could just split the values on spaces and comma with IFS:
IFS=', ' read -ra arr <<<"${arr[*]}"
for i in "${arr[#]}"; do
echo "$i"
done
or even like:
while IFS= read -r i; do
echo "$i"
done < <(IFS=', '; printf "%s\n" ${arr[*]})
... or like this
for item in "${arr[#]}"; {
sub=(${item//,/ })
echo "item1=${sub[0]}"
echo "item2=${sub[1]}"
}
with read
for item in "${arr[#]}"; {
read i1 i2 <<< ${item//,/ }
echo "item1=$i1"
echo "item2=$i2"
}
The trick is that we switch ',' to ' '(space) via this bash syntax ${item//,/ }(${var_name//pattern/replacement}) this is called variable substitution. And this one <<< is used to read directly from var.
A practical one-liner solution without using an apparent loop is
(IFS=', '; printf '%s\n' ${classNames[*]})
It runs in a subshell not to mess up with current shell's IFS.
I am trying to allocate a bunch of temp files in a loop and export them from within the loop. I then want to loop thru again and echo the values.
for (( i=1; i<=5; i++ ))
do
dd if=/dev/zero of=/tmp/mh1_$i.out bs=1024 count=1024 status=none
declare TEMP_FILE_${i}="/tmp/mh1_${i}.out"
export "TEMP_FILE_${i}"
done
If I do a echo $TEMP_FILE_1 it correctly prints /tmp/mh1_1.out
But when I try this in a loop it prints 1, 2, 3, 4 and 5.
for (( i=1; i<=5; i++ ))
do
echo $TEMP_FILE_${i} --> This prints the i value instead of /tmp/mh1_x.out
done
How do I escape the index $i in the echo above to see the real file name ?
I suggest using the mktemp utility with arrays:
# create tmp files
tmp_files=()
for ((i=0;i<5;++i)); do
tmp_files+=("$(mktemp --tmpdir mh1_XXXXXX.out)") || exit 1
done
# process all tmp files
for file in "${tmp_files[#]}"; do
echo "$file"
# do something with the file ...
done
# list all tmp files
printf '%s\n' "${tmp_files[#]}"
# list 2nd tmp file
echo "${tmp_files[1]}"
In order to make your code work, you need to use variable indirection and change your second for loop to:
for ((i=1;i<=5;++i)); do
var=temp_file_$i
echo "${!var}"
done
Don't use uppercase variables as they could clash with environmental or internal shell variables. Also, note that export is needed only when you want to pass the variables to child processes in the environment.
Why not use bash arrays?
export temp_file
loop
temp_file[$i]="/tmp/mh1_${i}.out"
...
endloop
Then loop over the array
I am trying to print true 10 times using a var and its not working
count=10
printf 'true\n%.0s' {1..$count}
This works:
printf 'true\n%.0s' {1..10}
I understand that {} are evaluated before vars but I cannot get around it.
That's not a problem with printf, it's a problem with {1..$count}. That expansion can only be done with constants.
for ((i=1; i<=10; i++)); do
printf 'true\n%.0s' "$i"
done
...or, if you really want to expand onto a single command line, collect your arguments into an array first:
arr=()
for ((i=1; i<=10; i++)); do arr+=( "$i" ); done
printf 'true\n%.0s' "${arr[#]}"
To explain why: Brace expansion ({1..10}) happens before parameter expansion ($count). Thus, by the time $count is expanded to 10, no more brace expansion is going to occur.
The other way (using an external process):
printf 'true\n%.0s' $(seq $count)
For the fun of it, here's a slightly bizarre way:
mapfile -n $count a < /dev/urandom; printf 'true\n%.0s' ${!a[#]}
read http://www.cyberciti.biz/faq/unix-linux-iterate-over-a-variable-range-of-numbers-in-bash/
the way to fix this to work is:
printf 'true\n%.0s' $(eval echo "{1..$count}")
I was trying to emulate the C enum semantics with arrays but without much sucess, basically i want to be able to iterate through a set of items and also declare a variable just by using an identifier like this:
$ bash -version
GNU bash, versión 4.1.10(4)-release (i686-pc-cygwin)
bad attempt:
#!/bin/bash
STATES=(INITIAL DEFAULT_CS_SETUP CREATED_CS CHECKED_OUT_DIR MKELEMENT_FILE\
CREATED_BRANCH CHECKED_IN_DIR COMPLETE)
tam=${#STATES[#]}
dereference()
{
tam=${#STATES[#]}
for ((j=0; j < $tam; j++)); do
if [[ "$state" == ${STATES[j]} ]];then
echo $j
break
fi
done
}
echo get the INITIAL state
state=INITIAL
echo ${STATES[`dereference`]}
echo get the next state from CREATED_CS
state=CREATED_CS
echo ${STATES[`dereference`+1]}
echo list elements from CREATED_CS to the end
state=CREATED_CS
for ((i=`dereference`; i < $tam; i++)); do
echo ${STATES[$i]}
done
echo list elements from CREATED_CS to CREATED_BRANCH is really awkward
state=CREATED_BRANCH
tmp_ind=`dereference`
state=CREATED_CS
for ((i=`dereference`; i <= $tmp_ind; i++)); do
echo ${STATES[$i]}
done
output:
get the INITIAL state
INITIAL
get the next state from CREATED_CS
CHECKED_OUT_DIR
list elements from CREATED_CS to the end
CREATED_CS
CHECKED_OUT_DIR
MKELEMENT_FILE
CREATED_BRANCH
CHECKED_IN_DIR
COMPLETE
list elements from CREATED_CS to CREATED_BRANCH is really awkward
CREATED_CS
CHECKED_OUT_DIR
MKELEMENT_FILE
CREATED_BRANCH
The bottom question is not about the correctness of the above code, instead i have a script with states and transitions, and i dont want to remember each state by and index, i want to use labels instead.
Thanks!
If you are happy with using a few $ signs here and there, how about you declare some constants:
#!/bin/bash
STATES=(INITIAL DEFAULT_CS_SETUP CREATED_CS CHECKED_OUT_DIR MKELEMENT_FILE CREATED_BRANCH CHECKED_IN_DIR COMPLETE)
tam=${#STATES[#]}
for ((i=0; i < $tam; i++)); do
name=${STATES[i]}
declare -r ${name}=$i
done
echo get the INITIAL state
echo ${STATES[$INITIAL]}
echo get the next state from CREATED_CS
echo ${STATES[$CREATED_CS+1]}
echo list elements from CREATED_CS to the end
for ((i=$CREATED_CS; i < $tam; i++)); do
echo ${STATES[$i]}
done
echo list elements from CREATED_CS to CREATED_BRANCH
for ((i=$CREATED_CS; i <= $CREATED_BRANCH; i++)); do
echo ${STATES[$i]}
done
In my script I need to expand an interval, e.g.:
input: 1,5-7
to get something like the following:
output: 1,5,6,7
I've found other solutions here, but they involve python and I can't use it in my script.
Solution with Just Bash 4 Builtins
You can use Bash range expansions. For example, assuming you've already parsed your input you can perform a series of successive operations to transform your range into a comma-separated series. For example:
value1=1
value2='5-7'
value2=${value2/-/..}
value2=`eval echo {$value2}`
echo "input: $value1,${value2// /,}"
All the usual caveats about the dangers of eval apply, and you'd definitely be better off solving this problem in Perl, Ruby, Python, or AWK. If you can't or won't, then you should at least consider including some pipeline tools like tr or sed in your conversions to avoid the need for eval.
Try something like this:
#!/bin/bash
for f in ${1//,/ }; do
if [[ $f =~ - ]]; then
a+=( $(seq ${f%-*} 1 ${f#*-}) )
else
a+=( $f )
fi
done
a=${a[*]}
a=${a// /,}
echo $a
Edit: As #Maxim_united mentioned in the comments, appending might be preferable to re-creating the array over and over again.
This should work with multiple ranges too.
#! /bin/bash
input="1,5-7,13-18,22"
result_str=""
for num in $(tr ',' ' ' <<< "$input"); do
if [[ "$num" == *-* ]]; then
res=$(seq -s ',' $(sed -n 's#\([0-9]\+\)-\([0-9]\+\).*#\1 \2#p' <<< "$num"))
else
res="$num"
fi
result_str="$result_str,$res"
done
echo ${result_str:1}
Will produce the following output:
1,5,6,7,13,14,15,16,17,18,22
expand_commas()
{
local arg
local st en i
set -- ${1//,/ }
for arg
do
case $arg in
[0-9]*-[0-9]*)
st=${arg%-*}
en=${arg#*-}
for ((i = st; i <= en; i++))
do
echo $i
done
;;
*)
echo $arg
;;
esac
done
}
Usage:
result=$(expand_commas arg)
eg:
result=$(expand_commas 1,5-7,9-12,3)
echo $result
You'll have to turn the separated words back into commas, of course.
It's a bit fragile with bad inputs but it's entirely in bash.
Here's my stab at it:
input=1,5-7,10,17-20
IFS=, read -a chunks <<< "$input"
output=()
for chunk in "${chunks[#]}"
do
IFS=- read -a args <<< "$chunk"
if (( ${#args[#]} == 1 )) # single number
then
output+=(${args[*]})
else # range
output+=($(seq "${args[#]}"))
fi
done
joined=$(sed -e 's/ /,/g' <<< "${output[*]}")
echo $joined
Basically split on commas, then interpret each piece. Then join back together with commas at the end.
A generic bash solution using the sequence expression `{x..y}'
#!/bin/bash
function doIt() {
local inp="${#/,/ }"
declare -a args=( $(echo ${inp/-/..}) )
local item
local sep
for item in "${args[#]}"
do
case ${item} in
*..*) eval "for i in {${item}} ; do echo -n \${sep}\${i}; sep=, ; done";;
*) echo -n ${sep}${item};;
esac
sep=,
done
}
doIt "1,5-7"
Should work with any input following the sample in the question. Also with multiple occurrences of x-y
Use only bash builtins
Using ideas from both #Ansgar Wiechers and #CodeGnome:
input="1,5-7,13-18,22"
for s in ${input//,/ }
do
if [[ $f =~ - ]]
then
a+=( $(eval echo {${s//-/..}}) )
else
a+=( $s )
fi
done
oldIFS=$IFS; IFS=$','; echo "${a[*]}"; IFS=$oldIFS
Works in Bash 3
Considering all the other answers, I came up with this solution, which does not use any sub-shells (but one call to eval for brace expansion) or separate processes:
# range list is assumed to be in $1 (e.g. 1-3,5,9-13)
# convert $1 to an array of ranges ("1-3" "5" "9-13")
IFS=,
local range=($1)
unset IFS
list=() # initialize result list
local r
for r in "${range[#]}"; do
if [[ $r == *-* ]]; then
# if the range is of the form "x-y",
# * convert to a brace expression "{x..y}",
# * using eval, this gets expanded to "x" "x+1" … "y" and
# * append this to the list array
eval list+=( {${r/-/..}} )
else
# otherwise, it is a simple number and can be appended to the array
list+=($r)
fi
done
# test output
echo ${list[#]}