Robust function to set the default value of scalar / array variables - bash

I have been having trouble setting the values of variables that do not currently exist in the workspace. There is a very nice 1-liner that can do this when the value of variable is a scalar (see here), but it is unclear if it can work for array variables and other weird cases (see here).
I was hoping that someone with more Bash expertise could help me create a setToDefault function that could set any variable to a default value in the work space (in a general way that will work for scalars, arrays, file paths and so on).
A test case for how this should work is as follows:
variable_1=(1.00 2.00 3.00)
#variable_2 does not exist and should be set to the default value
#variable_3 does not exist and should be set to the default value
setToDefault variable_1 "a"`
setToDefault variable_2 ("a" "b" "b c")
setToDefault variable_3 "/filepath with spaces/bash will mess up/"
echo ${variable_1[0]}
1.00
echo ${variable_2[2]}
"b c"
echo ${variable_3[0]}
"/filepath with spaces/bash will mess up/"

function setToDefault {
foo=$1
if [ "${!foo}" ]
then
return
fi
bar=$(printf '%s\n' "${#:2}" | paste -sd $'\x1f')
if [ "$3" ]
then
IFS=$'\x1f' read -a $foo <<< "$bar"
else
read $foo <<< "$bar"
fi
}
variable_1=(1.00 2.00 3.00)
setToDefault variable_1 a
setToDefault variable_2 a b 'b c'
setToDefault variable_3 '/filepath with spaces/bash will mess up/'
echo "${variable_1[0]}"
echo "${variable_2[2]}"
echo "${variable_3[0]}"
Result
1.00
b c
/filepath with spaces/bash will mess up/

Here's a pure Bash possibility:
set_to_default() {
# $1 is variable name
# $2, ... are arguments
# If variable pointed by $1 is set then nothing happens
# Otherwise, variable with name $1 is set to the value determined
# by the subsequent parameters:
# * if only $2 is present, variable is set to that value
# * if $2, $3... are present, then variable is set to an
# array with fields $2, $3, ...
(($#<2)) && return 1
local varname=$1
declare -p "$varname" &>/dev/null && return 0
shift
if (( $#==1 )); then
printf -v "$varname" '%s' "$1"
else
declare -ag "$varname=( \"\$#\" )"
fi
}
Basic check:
$ variable_1=( 1.00 2.00 3.00 )
$ set_to_default variable_1 "a"
$ set_to_default variable_2 "a" "b" "b c"
$ set_to_default variable_3 "/filepath with spaces/bash will mess up/"
$ declare -p "${!variable_#}"
declare -a variable_1='([0]="1.00" [1]="2.00" [2]="3.00")'
declare -a variable_2='([0]="a" [1]="b" [2]="b c")'
declare -- variable_3="/filepath with spaces/bash will mess up/"
Also works with embedded newlines and any funny character you can imagine:
$ set_to_default banana $'a newline\nhere' '*' '' ' '
$ declare -p banana
declare -a banana='([0]="a newline
here" [1]="*" [2]="" [3]=" ")'
If you want to set an array with only one field, first declare it as an array. Compare:
$ unset banana
$ set_to_default banana gorilla
$ declare -p banana
declare -- banana="gorilla"
$ unset banana
$ declare -a banana
$ set_to_default banana gorilla
$ declare -p banana
declare -a banana='([0]="gorilla")'
Bash<4
Wait, I just read in a comment that you have Bash 3.2, so this won't work because of the -g flag to declare. Then you'll have to explicit the loop:
set_to_default() {
# $1 is variable name
# $2, ... are arguments
# If variable pointed by $1 is set then nothing happens
# Otherwise, variable with name $1 is set to the value determined
# by the subsequent parameters:
# * if only $2 is present, variable is set to that value
# * if $2, $3... are present, then variable is set to an
# array with fields $2, $3, ...
(($#<2)) && return 1
local varname=$1 i
declare -p "$varname" &>/dev/null && return 0
shift
if (( $#==1 )); then
printf -v "$varname" '%s' "$1"
else
i=0
while IFS= read -r -d '' "$varname[$i]"; do ((++i)); done < <(printf '%s\0' "$#")
fi
}

Related

for loop for iterating over a list of string [duplicate]

I want to write a script that loops through 15 strings (array possibly?) Is that possible?
Something like:
for databaseName in listOfNames
then
# Do something
end
You can use it like this:
## declare an array variable
declare -a arr=("element1" "element2" "element3")
## now loop through the above array
for i in "${arr[#]}"
do
echo "$i"
# or do whatever with individual element of the array
done
# You can access them using echo "${arr[0]}", "${arr[1]}" also
Also works for multi-line array declaration
declare -a arr=("element1"
"element2" "element3"
"element4"
)
That is possible, of course.
for databaseName in a b c d e f; do
# do something like: echo $databaseName
done
See Bash Loops for, while and until for details.
None of those answers include a counter...
#!/bin/bash
## declare an array variable
declare -a array=("one" "two" "three")
# get length of an array
arraylength=${#array[#]}
# use for loop to read all values and indexes
for (( i=0; i<${arraylength}; i++ ));
do
echo "index: $i, value: ${array[$i]}"
done
Output:
index: 0, value: one
index: 1, value: two
index: 2, value: three
Yes
for Item in Item1 Item2 Item3 Item4 ;
do
echo $Item
done
Output:
Item1
Item2
Item3
Item4
To preserve spaces; single or double quote list entries and double quote list expansions.
for Item in 'Item 1' 'Item 2' 'Item 3' 'Item 4' ;
do
echo "$Item"
done
Output:
Item 1
Item 2
Item 3
Item 4
To make list over multiple lines
for Item in Item1 \
Item2 \
Item3 \
Item4
do
echo $Item
done
Output:
Item1
Item2
Item3
Item4
Simple list variable
List=( Item1 Item2 Item3 )
or
List=(
Item1
Item2
Item3
)
Display the list variable:
echo ${List[*]}
Output:
Item1 Item2 Item3
Loop through the list:
for Item in ${List[*]}
do
echo $Item
done
Output:
Item1
Item2
Item3
Create a function to go through a list:
Loop(){
for item in ${*} ;
do
echo ${item}
done
}
Loop ${List[*]}
Using the declare keyword (command) to create the list, which is technically called an array:
declare -a List=(
"element 1"
"element 2"
"element 3"
)
for entry in "${List[#]}"
do
echo "$entry"
done
Output:
element 1
element 2
element 3
Creating an associative array. A dictionary:
declare -A continent
continent[Vietnam]=Asia
continent[France]=Europe
continent[Argentina]=America
for item in "${!continent[#]}";
do
printf "$item is in ${continent[$item]} \n"
done
Output:
Argentina is in America
Vietnam is in Asia
France is in Europe
CSV variables or files in to a list. Changing the internal field separator from a space, to what ever you want. In the example below it is changed to a comma
List="Item 1,Item 2,Item 3"
Backup_of_internal_field_separator=$IFS
IFS=,
for item in $List;
do
echo $item
done
IFS=$Backup_of_internal_field_separator
Output:
Item 1
Item 2
Item 3
If need to number them:
`
this is called a back tick. Put the command inside back ticks.
`command`
It is next to the number one on your keyboard and or above the tab key, on a standard American English language keyboard.
List=()
Start_count=0
Step_count=0.1
Stop_count=1
for Item in `seq $Start_count $Step_count $Stop_count`
do
List+=(Item_$Item)
done
for Item in ${List[*]}
do
echo $Item
done
Output is:
Item_0.0
Item_0.1
Item_0.2
Item_0.3
Item_0.4
Item_0.5
Item_0.6
Item_0.7
Item_0.8
Item_0.9
Item_1.0
Becoming more familiar with bashes behavior:
Create a list in a file
cat <<EOF> List_entries.txt
Item1
Item 2
'Item 3'
"Item 4"
Item 7 : *
"Item 6 : * "
"Item 6 : *"
Item 8 : $PWD
'Item 8 : $PWD'
"Item 9 : $PWD"
EOF
Read the list file in to a list and display
List=$(cat List_entries.txt)
echo $List
echo '$List'
echo "$List"
echo ${List[*]}
echo '${List[*]}'
echo "${List[*]}"
echo ${List[#]}
echo '${List[#]}'
echo "${List[#]}"
BASH commandline reference manual: Special meaning of certain characters or words to the shell.
In the same spirit as 4ndrew's answer:
listOfNames="RA
RB
R C
RD"
# To allow for other whitespace in the string:
# 1. add double quotes around the list variable, or
# 2. see the IFS note (under 'Side Notes')
for databaseName in "$listOfNames" # <-- Note: Added "" quotes.
do
echo "$databaseName" # (i.e. do action / processing of $databaseName here...)
done
# Outputs
# RA
# RB
# R C
# RD
B. No whitespace in the names:
listOfNames="RA
RB
R C
RD"
for databaseName in $listOfNames # Note: No quotes
do
echo "$databaseName" # (i.e. do action / processing of $databaseName here...)
done
# Outputs
# RA
# RB
# R
# C
# RD
Notes
In the second example, using listOfNames="RA RB R C RD" has the same output.
Other ways to bring in data include:
stdin (listed below),
variables,
an array (the accepted answer),
a file...
Read from stdin
# line delimited (each databaseName is stored on a line)
while read databaseName
do
echo "$databaseName" # i.e. do action / processing of $databaseName here...
done # <<< or_another_input_method_here
the bash IFS "field separator to line" [1] delimiter can be specified in the script to allow other whitespace (i.e. IFS='\n', or for MacOS IFS='\r')
I like the accepted answer also :) -- I've include these snippets as other helpful ways that also answer the question.
Including #!/bin/bash at the top of the script file indicates the execution environment.
It took me months to figure out how to code this simply :)
Other Sources
(while read loop)
You can use the syntax of ${arrayName[#]}
#!/bin/bash
# declare an array called files, that contains 3 values
files=( "/etc/passwd" "/etc/group" "/etc/hosts" )
for i in "${files[#]}"
do
echo "$i"
done
Surprised that nobody's posted this yet -- if you need the indices of the elements while you're looping through the array, you can do this:
arr=(foo bar baz)
for i in ${!arr[#]}
do
echo $i "${arr[i]}"
done
Output:
0 foo
1 bar
2 baz
I find this a lot more elegant than the "traditional" for-loop style (for (( i=0; i<${#arr[#]}; i++ ))).
(${!arr[#]} and $i don't need to be quoted because they're just numbers; some would suggest quoting them anyway, but that's just personal preference.)
This is also easy to read:
FilePath=(
"/tmp/path1/" #FilePath[0]
"/tmp/path2/" #FilePath[1]
)
#Loop
for Path in "${FilePath[#]}"
do
echo "$Path"
done
I used this approach for my GitHub updates, and I found it simple.
## declare an array variable
arr_variable=("kofi" "kwame" "Ama")
## now loop through the above array
for i in "${arr_variable[#]}"
do
echo "$i"
done
You can iterate through bash array values using a counter with three-expression (C style) to read all values and indexes for loops syntax:
declare -a kofi=("kofi" "kwame" "Ama")
# get the length of the array
length=${#kofi[#]}
for (( j=0; j<${length}; j++ ));
do
print (f "Current index %d with value %s\n" $j "${kofi[$j]}")
done
Simple way :
arr=("sharlock" "bomkesh" "feluda" ) ##declare array
len=${#arr[*]} # it returns the array length
#iterate with while loop
i=0
while [ $i -lt $len ]
do
echo ${arr[$i]}
i=$((i+1))
done
#iterate with for loop
for i in $arr
do
echo $i
done
#iterate with splice
echo ${arr[#]:0:3}
listOfNames="db_one db_two db_three"
for databaseName in $listOfNames
do
echo $databaseName
done
or just
for databaseName in db_one db_two db_three
do
echo $databaseName
done
Implicit array for script or functions:
In addition to anubhava's correct answer: If basic syntax for loop is:
for var in "${arr[#]}" ;do ...$var... ;done
there is a special case in bash:
When running a script or a function, arguments passed at command lines will be assigned to $# array variable, you can access by $1, $2, $3, and so on.
This can be populated (for test) by
set -- arg1 arg2 arg3 ...
A loop over this array could be written simply:
for item ;do
echo "This is item: $item."
done
Note that the reserved work in is not present and no array name too!
Sample:
set -- arg1 arg2 arg3 ...
for item ;do
echo "This is item: $item."
done
This is item: arg1.
This is item: arg2.
This is item: arg3.
This is item: ....
Note that this is same than
for item in "$#";do
echo "This is item: $item."
done
Then into a script:
#!/bin/bash
for item ;do
printf "Doing something with '%s'.\n" "$item"
done
Save this in a script myscript.sh, chmod +x myscript.sh, then
./myscript.sh arg1 arg2 arg3 ...
Doing something with 'arg1'.
Doing something with 'arg2'.
Doing something with 'arg3'.
Doing something with '...'.
Same in a function:
myfunc() { for item;do cat <<<"Working about '$item'."; done ; }
Then
myfunc item1 tiem2 time3
Working about 'item1'.
Working about 'tiem2'.
Working about 'time3'.
The declare array doesn't work for Korn shell. Use the below example for the Korn shell:
promote_sla_chk_lst="cdi xlob"
set -A promote_arry $promote_sla_chk_lst
for i in ${promote_arry[*]};
do
echo $i
done
Try this. It is working and tested.
for k in "${array[#]}"
do
echo $k
done
# For accessing with the echo command: echo ${array[0]}, ${array[1]}
This is similar to user2533809's answer, but each file will be executed as a separate command.
#!/bin/bash
names="RA
RB
R C
RD"
while read -r line; do
echo line: "$line"
done <<< "$names"
If you are using Korn shell, there is "set -A databaseName ", else there is "declare -a databaseName"
To write a script working on all shells,
set -A databaseName=("db1" "db2" ....) ||
declare -a databaseName=("db1" "db2" ....)
# now loop
for dbname in "${arr[#]}"
do
echo "$dbname" # or whatever
done
It should be work on all shells.
What I really needed for this was something like this:
for i in $(the_array); do something; done
For instance:
for i in $(ps -aux | grep vlc | awk '{ print $2 }'); do kill -9 $i; done
(Would kill all processes with vlc in their name)
How you loop through an array, depends on the presence of new line characters. With new line characters separating the array elements, the array can be referred to as "$array", otherwise it should be referred to as "${array[#]}". The following script will make it clear:
#!/bin/bash
mkdir temp
mkdir temp/aaa
mkdir temp/bbb
mkdir temp/ccc
array=$(ls temp)
array1=(aaa bbb ccc)
array2=$(echo -e "aaa\nbbb\nccc")
echo '$array'
echo "$array"
echo
for dirname in "$array"; do
echo "$dirname"
done
echo
for dirname in "${array[#]}"; do
echo "$dirname"
done
echo
echo '$array1'
echo "$array1"
echo
for dirname in "$array1"; do
echo "$dirname"
done
echo
for dirname in "${array1[#]}"; do
echo "$dirname"
done
echo
echo '$array2'
echo "$array2"
echo
for dirname in "$array2"; do
echo "$dirname"
done
echo
for dirname in "${array2[#]}"; do
echo "$dirname"
done
rmdir temp/aaa
rmdir temp/bbb
rmdir temp/ccc
rmdir temp
Possible first line of every Bash script/session:
say() { for line in "${#}" ; do printf "%s\n" "${line}" ; done ; }
Use e.g.:
$ aa=( 7 -4 -e ) ; say "${aa[#]}"
7
-4
-e
May consider: echo interprets -e as option here
Single line looping,
declare -a listOfNames=('db_a' 'db_b' 'db_c')
for databaseName in ${listOfNames[#]}; do echo $databaseName; done;
you will get an output like this,
db_a
db_b
db_c
I loop through an array of my projects for a git pull update:
#!/bin/sh
projects="
web
ios
android
"
for project in $projects do
cd $HOME/develop/$project && git pull
end

assign the result of find to a array [duplicate]

I want to write a script that loops through 15 strings (array possibly?) Is that possible?
Something like:
for databaseName in listOfNames
then
# Do something
end
You can use it like this:
## declare an array variable
declare -a arr=("element1" "element2" "element3")
## now loop through the above array
for i in "${arr[#]}"
do
echo "$i"
# or do whatever with individual element of the array
done
# You can access them using echo "${arr[0]}", "${arr[1]}" also
Also works for multi-line array declaration
declare -a arr=("element1"
"element2" "element3"
"element4"
)
That is possible, of course.
for databaseName in a b c d e f; do
# do something like: echo $databaseName
done
See Bash Loops for, while and until for details.
None of those answers include a counter...
#!/bin/bash
## declare an array variable
declare -a array=("one" "two" "three")
# get length of an array
arraylength=${#array[#]}
# use for loop to read all values and indexes
for (( i=0; i<${arraylength}; i++ ));
do
echo "index: $i, value: ${array[$i]}"
done
Output:
index: 0, value: one
index: 1, value: two
index: 2, value: three
Yes
for Item in Item1 Item2 Item3 Item4 ;
do
echo $Item
done
Output:
Item1
Item2
Item3
Item4
To preserve spaces; single or double quote list entries and double quote list expansions.
for Item in 'Item 1' 'Item 2' 'Item 3' 'Item 4' ;
do
echo "$Item"
done
Output:
Item 1
Item 2
Item 3
Item 4
To make list over multiple lines
for Item in Item1 \
Item2 \
Item3 \
Item4
do
echo $Item
done
Output:
Item1
Item2
Item3
Item4
Simple list variable
List=( Item1 Item2 Item3 )
or
List=(
Item1
Item2
Item3
)
Display the list variable:
echo ${List[*]}
Output:
Item1 Item2 Item3
Loop through the list:
for Item in ${List[*]}
do
echo $Item
done
Output:
Item1
Item2
Item3
Create a function to go through a list:
Loop(){
for item in ${*} ;
do
echo ${item}
done
}
Loop ${List[*]}
Using the declare keyword (command) to create the list, which is technically called an array:
declare -a List=(
"element 1"
"element 2"
"element 3"
)
for entry in "${List[#]}"
do
echo "$entry"
done
Output:
element 1
element 2
element 3
Creating an associative array. A dictionary:
declare -A continent
continent[Vietnam]=Asia
continent[France]=Europe
continent[Argentina]=America
for item in "${!continent[#]}";
do
printf "$item is in ${continent[$item]} \n"
done
Output:
Argentina is in America
Vietnam is in Asia
France is in Europe
CSV variables or files in to a list. Changing the internal field separator from a space, to what ever you want. In the example below it is changed to a comma
List="Item 1,Item 2,Item 3"
Backup_of_internal_field_separator=$IFS
IFS=,
for item in $List;
do
echo $item
done
IFS=$Backup_of_internal_field_separator
Output:
Item 1
Item 2
Item 3
If need to number them:
`
this is called a back tick. Put the command inside back ticks.
`command`
It is next to the number one on your keyboard and or above the tab key, on a standard American English language keyboard.
List=()
Start_count=0
Step_count=0.1
Stop_count=1
for Item in `seq $Start_count $Step_count $Stop_count`
do
List+=(Item_$Item)
done
for Item in ${List[*]}
do
echo $Item
done
Output is:
Item_0.0
Item_0.1
Item_0.2
Item_0.3
Item_0.4
Item_0.5
Item_0.6
Item_0.7
Item_0.8
Item_0.9
Item_1.0
Becoming more familiar with bashes behavior:
Create a list in a file
cat <<EOF> List_entries.txt
Item1
Item 2
'Item 3'
"Item 4"
Item 7 : *
"Item 6 : * "
"Item 6 : *"
Item 8 : $PWD
'Item 8 : $PWD'
"Item 9 : $PWD"
EOF
Read the list file in to a list and display
List=$(cat List_entries.txt)
echo $List
echo '$List'
echo "$List"
echo ${List[*]}
echo '${List[*]}'
echo "${List[*]}"
echo ${List[#]}
echo '${List[#]}'
echo "${List[#]}"
BASH commandline reference manual: Special meaning of certain characters or words to the shell.
In the same spirit as 4ndrew's answer:
listOfNames="RA
RB
R C
RD"
# To allow for other whitespace in the string:
# 1. add double quotes around the list variable, or
# 2. see the IFS note (under 'Side Notes')
for databaseName in "$listOfNames" # <-- Note: Added "" quotes.
do
echo "$databaseName" # (i.e. do action / processing of $databaseName here...)
done
# Outputs
# RA
# RB
# R C
# RD
B. No whitespace in the names:
listOfNames="RA
RB
R C
RD"
for databaseName in $listOfNames # Note: No quotes
do
echo "$databaseName" # (i.e. do action / processing of $databaseName here...)
done
# Outputs
# RA
# RB
# R
# C
# RD
Notes
In the second example, using listOfNames="RA RB R C RD" has the same output.
Other ways to bring in data include:
stdin (listed below),
variables,
an array (the accepted answer),
a file...
Read from stdin
# line delimited (each databaseName is stored on a line)
while read databaseName
do
echo "$databaseName" # i.e. do action / processing of $databaseName here...
done # <<< or_another_input_method_here
the bash IFS "field separator to line" [1] delimiter can be specified in the script to allow other whitespace (i.e. IFS='\n', or for MacOS IFS='\r')
I like the accepted answer also :) -- I've include these snippets as other helpful ways that also answer the question.
Including #!/bin/bash at the top of the script file indicates the execution environment.
It took me months to figure out how to code this simply :)
Other Sources
(while read loop)
You can use the syntax of ${arrayName[#]}
#!/bin/bash
# declare an array called files, that contains 3 values
files=( "/etc/passwd" "/etc/group" "/etc/hosts" )
for i in "${files[#]}"
do
echo "$i"
done
Surprised that nobody's posted this yet -- if you need the indices of the elements while you're looping through the array, you can do this:
arr=(foo bar baz)
for i in ${!arr[#]}
do
echo $i "${arr[i]}"
done
Output:
0 foo
1 bar
2 baz
I find this a lot more elegant than the "traditional" for-loop style (for (( i=0; i<${#arr[#]}; i++ ))).
(${!arr[#]} and $i don't need to be quoted because they're just numbers; some would suggest quoting them anyway, but that's just personal preference.)
This is also easy to read:
FilePath=(
"/tmp/path1/" #FilePath[0]
"/tmp/path2/" #FilePath[1]
)
#Loop
for Path in "${FilePath[#]}"
do
echo "$Path"
done
I used this approach for my GitHub updates, and I found it simple.
## declare an array variable
arr_variable=("kofi" "kwame" "Ama")
## now loop through the above array
for i in "${arr_variable[#]}"
do
echo "$i"
done
You can iterate through bash array values using a counter with three-expression (C style) to read all values and indexes for loops syntax:
declare -a kofi=("kofi" "kwame" "Ama")
# get the length of the array
length=${#kofi[#]}
for (( j=0; j<${length}; j++ ));
do
print (f "Current index %d with value %s\n" $j "${kofi[$j]}")
done
Simple way :
arr=("sharlock" "bomkesh" "feluda" ) ##declare array
len=${#arr[*]} # it returns the array length
#iterate with while loop
i=0
while [ $i -lt $len ]
do
echo ${arr[$i]}
i=$((i+1))
done
#iterate with for loop
for i in $arr
do
echo $i
done
#iterate with splice
echo ${arr[#]:0:3}
listOfNames="db_one db_two db_three"
for databaseName in $listOfNames
do
echo $databaseName
done
or just
for databaseName in db_one db_two db_three
do
echo $databaseName
done
Implicit array for script or functions:
In addition to anubhava's correct answer: If basic syntax for loop is:
for var in "${arr[#]}" ;do ...$var... ;done
there is a special case in bash:
When running a script or a function, arguments passed at command lines will be assigned to $# array variable, you can access by $1, $2, $3, and so on.
This can be populated (for test) by
set -- arg1 arg2 arg3 ...
A loop over this array could be written simply:
for item ;do
echo "This is item: $item."
done
Note that the reserved work in is not present and no array name too!
Sample:
set -- arg1 arg2 arg3 ...
for item ;do
echo "This is item: $item."
done
This is item: arg1.
This is item: arg2.
This is item: arg3.
This is item: ....
Note that this is same than
for item in "$#";do
echo "This is item: $item."
done
Then into a script:
#!/bin/bash
for item ;do
printf "Doing something with '%s'.\n" "$item"
done
Save this in a script myscript.sh, chmod +x myscript.sh, then
./myscript.sh arg1 arg2 arg3 ...
Doing something with 'arg1'.
Doing something with 'arg2'.
Doing something with 'arg3'.
Doing something with '...'.
Same in a function:
myfunc() { for item;do cat <<<"Working about '$item'."; done ; }
Then
myfunc item1 tiem2 time3
Working about 'item1'.
Working about 'tiem2'.
Working about 'time3'.
The declare array doesn't work for Korn shell. Use the below example for the Korn shell:
promote_sla_chk_lst="cdi xlob"
set -A promote_arry $promote_sla_chk_lst
for i in ${promote_arry[*]};
do
echo $i
done
Try this. It is working and tested.
for k in "${array[#]}"
do
echo $k
done
# For accessing with the echo command: echo ${array[0]}, ${array[1]}
This is similar to user2533809's answer, but each file will be executed as a separate command.
#!/bin/bash
names="RA
RB
R C
RD"
while read -r line; do
echo line: "$line"
done <<< "$names"
If you are using Korn shell, there is "set -A databaseName ", else there is "declare -a databaseName"
To write a script working on all shells,
set -A databaseName=("db1" "db2" ....) ||
declare -a databaseName=("db1" "db2" ....)
# now loop
for dbname in "${arr[#]}"
do
echo "$dbname" # or whatever
done
It should be work on all shells.
What I really needed for this was something like this:
for i in $(the_array); do something; done
For instance:
for i in $(ps -aux | grep vlc | awk '{ print $2 }'); do kill -9 $i; done
(Would kill all processes with vlc in their name)
How you loop through an array, depends on the presence of new line characters. With new line characters separating the array elements, the array can be referred to as "$array", otherwise it should be referred to as "${array[#]}". The following script will make it clear:
#!/bin/bash
mkdir temp
mkdir temp/aaa
mkdir temp/bbb
mkdir temp/ccc
array=$(ls temp)
array1=(aaa bbb ccc)
array2=$(echo -e "aaa\nbbb\nccc")
echo '$array'
echo "$array"
echo
for dirname in "$array"; do
echo "$dirname"
done
echo
for dirname in "${array[#]}"; do
echo "$dirname"
done
echo
echo '$array1'
echo "$array1"
echo
for dirname in "$array1"; do
echo "$dirname"
done
echo
for dirname in "${array1[#]}"; do
echo "$dirname"
done
echo
echo '$array2'
echo "$array2"
echo
for dirname in "$array2"; do
echo "$dirname"
done
echo
for dirname in "${array2[#]}"; do
echo "$dirname"
done
rmdir temp/aaa
rmdir temp/bbb
rmdir temp/ccc
rmdir temp
Possible first line of every Bash script/session:
say() { for line in "${#}" ; do printf "%s\n" "${line}" ; done ; }
Use e.g.:
$ aa=( 7 -4 -e ) ; say "${aa[#]}"
7
-4
-e
May consider: echo interprets -e as option here
Single line looping,
declare -a listOfNames=('db_a' 'db_b' 'db_c')
for databaseName in ${listOfNames[#]}; do echo $databaseName; done;
you will get an output like this,
db_a
db_b
db_c
I loop through an array of my projects for a git pull update:
#!/bin/sh
projects="
web
ios
android
"
for project in $projects do
cd $HOME/develop/$project && git pull
end

Substition in Bash and associative array

I want to dynamically declare and unset associative array, but arrays drive me crazy and they do have the best driving licence. :-(
names=( Charlie Snoopy Linux Marcia )
intestines=$(printf "%s\n" ${names[#]} | awk '{ print "["$1"]="FNR }' | tr "\n" " ")
echo $intestines # ok: [Charlie]=1 [Snoopy]=2 [Linux]=3 [Marcia]=4
unset namesAssociative
declare -A namesAssociative=( [Charlie]=1 [Snoopy]=2 [Linux]=3 [Marcia]=4 ) # works ok
echo ${namesAssociative[Linux]} # OK: 3
But:
unset namesAssociative
declare -A namesAssociative=( $intestines ) # error
exec "declare -A namesAssociative=( $intestines )" # error
declare -A namesAssociative=( $(printf "%s\n" ${names[#]} | awk '{ print "["$1"]="FNR }' | tr "\n" " ") ) # error
etc...
I guess God punishes me that I have not written that in Python from the very begining... :-)
This works as you expect and is made safe using the %q format indicator for the associative array's keys.
#!/usr/bin/env bash
names=( Charlie Snoopy Linux Marcia )
# shellcheck disable=SC2155 # Intended dynamic declaration
declare -A namesAssociative="($(
for i in "${!names[#]}"; do
printf '[%q]=%d ' "${names[i]}" $((i + 1))
done
))"
declare -p namesAssociative
or Alternatively if your names array is not sparse:
declare -A namesAssociative="($(
i=1
for k in "${names[#]}"; do
printf '[%q]=%d ' "$k" $((i++))
done
))"
Easier demo, putting associative array name arr in list:
assoc_list="arr[first]=1 arr[second]=2 arr[third]=3"
unset arr
declare -A arr
eval $assoc_list
echo ${arr[second]}
2
Another demo setting associative array name arr later:
assoc_list="[first]=1 [second]=2 [third]=3"
# set associate array name as variable with value arr
assoc_arr_name=arr
# create assoc_array_list from assoc_list
assoc_array_list=${assoc_list//[/$assoc_arr_name[}
echo $assoc_array_list
arr[first]=1 arr[second]=2 arr[third]=3
unset $assoc_arr_name
declare -A $assoc_arr_name
eval $assoc_array_list
echo ${arr[second]}
2
In more general way:
keys_arr=(first second third)
values_arr=(1 2 3)
map_name=assoc_arr
unset $map_name
declare -A $map_name
for ((i = 0 ; i < ${#keys_arr[#]} ; i++)); do
eval $map_name[${keys_arr[i]}]=${values_arr[i]};
done
echo ${assoc_arr[second]}
2
You are insisting too strongly on creating the array from a dynamic string literal in one go, which, depending on various peculiarities of the order of expansion, may require an eval or the like.
Maybe don’t push it that hard and use a for-cycle instead:
declare -a names=( Charlie Snoopy Linux Marcia )
declare -Ai indices1 indices2 # to show two different options
declare -i index
for index in "${!names[#]}"; do
indices1["${names[index]}"]=$((index + 1)) # option 1
indices2+=(["${names[index]}"]=$((index + 1))) # option 2
done
((${#indices1[#]} == ${#indices2[#]})) || echo heck blah
for name in "${!indices1[#]}"; do
echo "${name}: $((indices1["${name}"])) $((indices2["${name}"]))"
done

How to use a shell variable to list files from multiple folders [duplicate]

I want to write a script that loops through 15 strings (array possibly?) Is that possible?
Something like:
for databaseName in listOfNames
then
# Do something
end
You can use it like this:
## declare an array variable
declare -a arr=("element1" "element2" "element3")
## now loop through the above array
for i in "${arr[#]}"
do
echo "$i"
# or do whatever with individual element of the array
done
# You can access them using echo "${arr[0]}", "${arr[1]}" also
Also works for multi-line array declaration
declare -a arr=("element1"
"element2" "element3"
"element4"
)
That is possible, of course.
for databaseName in a b c d e f; do
# do something like: echo $databaseName
done
See Bash Loops for, while and until for details.
None of those answers include a counter...
#!/bin/bash
## declare an array variable
declare -a array=("one" "two" "three")
# get length of an array
arraylength=${#array[#]}
# use for loop to read all values and indexes
for (( i=0; i<${arraylength}; i++ ));
do
echo "index: $i, value: ${array[$i]}"
done
Output:
index: 0, value: one
index: 1, value: two
index: 2, value: three
Yes
for Item in Item1 Item2 Item3 Item4 ;
do
echo $Item
done
Output:
Item1
Item2
Item3
Item4
To preserve spaces; single or double quote list entries and double quote list expansions.
for Item in 'Item 1' 'Item 2' 'Item 3' 'Item 4' ;
do
echo "$Item"
done
Output:
Item 1
Item 2
Item 3
Item 4
To make list over multiple lines
for Item in Item1 \
Item2 \
Item3 \
Item4
do
echo $Item
done
Output:
Item1
Item2
Item3
Item4
Simple list variable
List=( Item1 Item2 Item3 )
or
List=(
Item1
Item2
Item3
)
Display the list variable:
echo ${List[*]}
Output:
Item1 Item2 Item3
Loop through the list:
for Item in ${List[*]}
do
echo $Item
done
Output:
Item1
Item2
Item3
Create a function to go through a list:
Loop(){
for item in ${*} ;
do
echo ${item}
done
}
Loop ${List[*]}
Using the declare keyword (command) to create the list, which is technically called an array:
declare -a List=(
"element 1"
"element 2"
"element 3"
)
for entry in "${List[#]}"
do
echo "$entry"
done
Output:
element 1
element 2
element 3
Creating an associative array. A dictionary:
declare -A continent
continent[Vietnam]=Asia
continent[France]=Europe
continent[Argentina]=America
for item in "${!continent[#]}";
do
printf "$item is in ${continent[$item]} \n"
done
Output:
Argentina is in America
Vietnam is in Asia
France is in Europe
CSV variables or files in to a list. Changing the internal field separator from a space, to what ever you want. In the example below it is changed to a comma
List="Item 1,Item 2,Item 3"
Backup_of_internal_field_separator=$IFS
IFS=,
for item in $List;
do
echo $item
done
IFS=$Backup_of_internal_field_separator
Output:
Item 1
Item 2
Item 3
If need to number them:
`
this is called a back tick. Put the command inside back ticks.
`command`
It is next to the number one on your keyboard and or above the tab key, on a standard American English language keyboard.
List=()
Start_count=0
Step_count=0.1
Stop_count=1
for Item in `seq $Start_count $Step_count $Stop_count`
do
List+=(Item_$Item)
done
for Item in ${List[*]}
do
echo $Item
done
Output is:
Item_0.0
Item_0.1
Item_0.2
Item_0.3
Item_0.4
Item_0.5
Item_0.6
Item_0.7
Item_0.8
Item_0.9
Item_1.0
Becoming more familiar with bashes behavior:
Create a list in a file
cat <<EOF> List_entries.txt
Item1
Item 2
'Item 3'
"Item 4"
Item 7 : *
"Item 6 : * "
"Item 6 : *"
Item 8 : $PWD
'Item 8 : $PWD'
"Item 9 : $PWD"
EOF
Read the list file in to a list and display
List=$(cat List_entries.txt)
echo $List
echo '$List'
echo "$List"
echo ${List[*]}
echo '${List[*]}'
echo "${List[*]}"
echo ${List[#]}
echo '${List[#]}'
echo "${List[#]}"
BASH commandline reference manual: Special meaning of certain characters or words to the shell.
In the same spirit as 4ndrew's answer:
listOfNames="RA
RB
R C
RD"
# To allow for other whitespace in the string:
# 1. add double quotes around the list variable, or
# 2. see the IFS note (under 'Side Notes')
for databaseName in "$listOfNames" # <-- Note: Added "" quotes.
do
echo "$databaseName" # (i.e. do action / processing of $databaseName here...)
done
# Outputs
# RA
# RB
# R C
# RD
B. No whitespace in the names:
listOfNames="RA
RB
R C
RD"
for databaseName in $listOfNames # Note: No quotes
do
echo "$databaseName" # (i.e. do action / processing of $databaseName here...)
done
# Outputs
# RA
# RB
# R
# C
# RD
Notes
In the second example, using listOfNames="RA RB R C RD" has the same output.
Other ways to bring in data include:
stdin (listed below),
variables,
an array (the accepted answer),
a file...
Read from stdin
# line delimited (each databaseName is stored on a line)
while read databaseName
do
echo "$databaseName" # i.e. do action / processing of $databaseName here...
done # <<< or_another_input_method_here
the bash IFS "field separator to line" [1] delimiter can be specified in the script to allow other whitespace (i.e. IFS='\n', or for MacOS IFS='\r')
I like the accepted answer also :) -- I've include these snippets as other helpful ways that also answer the question.
Including #!/bin/bash at the top of the script file indicates the execution environment.
It took me months to figure out how to code this simply :)
Other Sources
(while read loop)
You can use the syntax of ${arrayName[#]}
#!/bin/bash
# declare an array called files, that contains 3 values
files=( "/etc/passwd" "/etc/group" "/etc/hosts" )
for i in "${files[#]}"
do
echo "$i"
done
Surprised that nobody's posted this yet -- if you need the indices of the elements while you're looping through the array, you can do this:
arr=(foo bar baz)
for i in ${!arr[#]}
do
echo $i "${arr[i]}"
done
Output:
0 foo
1 bar
2 baz
I find this a lot more elegant than the "traditional" for-loop style (for (( i=0; i<${#arr[#]}; i++ ))).
(${!arr[#]} and $i don't need to be quoted because they're just numbers; some would suggest quoting them anyway, but that's just personal preference.)
This is also easy to read:
FilePath=(
"/tmp/path1/" #FilePath[0]
"/tmp/path2/" #FilePath[1]
)
#Loop
for Path in "${FilePath[#]}"
do
echo "$Path"
done
I used this approach for my GitHub updates, and I found it simple.
## declare an array variable
arr_variable=("kofi" "kwame" "Ama")
## now loop through the above array
for i in "${arr_variable[#]}"
do
echo "$i"
done
You can iterate through bash array values using a counter with three-expression (C style) to read all values and indexes for loops syntax:
declare -a kofi=("kofi" "kwame" "Ama")
# get the length of the array
length=${#kofi[#]}
for (( j=0; j<${length}; j++ ));
do
print (f "Current index %d with value %s\n" $j "${kofi[$j]}")
done
Simple way :
arr=("sharlock" "bomkesh" "feluda" ) ##declare array
len=${#arr[*]} # it returns the array length
#iterate with while loop
i=0
while [ $i -lt $len ]
do
echo ${arr[$i]}
i=$((i+1))
done
#iterate with for loop
for i in $arr
do
echo $i
done
#iterate with splice
echo ${arr[#]:0:3}
listOfNames="db_one db_two db_three"
for databaseName in $listOfNames
do
echo $databaseName
done
or just
for databaseName in db_one db_two db_three
do
echo $databaseName
done
Implicit array for script or functions:
In addition to anubhava's correct answer: If basic syntax for loop is:
for var in "${arr[#]}" ;do ...$var... ;done
there is a special case in bash:
When running a script or a function, arguments passed at command lines will be assigned to $# array variable, you can access by $1, $2, $3, and so on.
This can be populated (for test) by
set -- arg1 arg2 arg3 ...
A loop over this array could be written simply:
for item ;do
echo "This is item: $item."
done
Note that the reserved work in is not present and no array name too!
Sample:
set -- arg1 arg2 arg3 ...
for item ;do
echo "This is item: $item."
done
This is item: arg1.
This is item: arg2.
This is item: arg3.
This is item: ....
Note that this is same than
for item in "$#";do
echo "This is item: $item."
done
Then into a script:
#!/bin/bash
for item ;do
printf "Doing something with '%s'.\n" "$item"
done
Save this in a script myscript.sh, chmod +x myscript.sh, then
./myscript.sh arg1 arg2 arg3 ...
Doing something with 'arg1'.
Doing something with 'arg2'.
Doing something with 'arg3'.
Doing something with '...'.
Same in a function:
myfunc() { for item;do cat <<<"Working about '$item'."; done ; }
Then
myfunc item1 tiem2 time3
Working about 'item1'.
Working about 'tiem2'.
Working about 'time3'.
The declare array doesn't work for Korn shell. Use the below example for the Korn shell:
promote_sla_chk_lst="cdi xlob"
set -A promote_arry $promote_sla_chk_lst
for i in ${promote_arry[*]};
do
echo $i
done
Try this. It is working and tested.
for k in "${array[#]}"
do
echo $k
done
# For accessing with the echo command: echo ${array[0]}, ${array[1]}
This is similar to user2533809's answer, but each file will be executed as a separate command.
#!/bin/bash
names="RA
RB
R C
RD"
while read -r line; do
echo line: "$line"
done <<< "$names"
If you are using Korn shell, there is "set -A databaseName ", else there is "declare -a databaseName"
To write a script working on all shells,
set -A databaseName=("db1" "db2" ....) ||
declare -a databaseName=("db1" "db2" ....)
# now loop
for dbname in "${arr[#]}"
do
echo "$dbname" # or whatever
done
It should be work on all shells.
What I really needed for this was something like this:
for i in $(the_array); do something; done
For instance:
for i in $(ps -aux | grep vlc | awk '{ print $2 }'); do kill -9 $i; done
(Would kill all processes with vlc in their name)
How you loop through an array, depends on the presence of new line characters. With new line characters separating the array elements, the array can be referred to as "$array", otherwise it should be referred to as "${array[#]}". The following script will make it clear:
#!/bin/bash
mkdir temp
mkdir temp/aaa
mkdir temp/bbb
mkdir temp/ccc
array=$(ls temp)
array1=(aaa bbb ccc)
array2=$(echo -e "aaa\nbbb\nccc")
echo '$array'
echo "$array"
echo
for dirname in "$array"; do
echo "$dirname"
done
echo
for dirname in "${array[#]}"; do
echo "$dirname"
done
echo
echo '$array1'
echo "$array1"
echo
for dirname in "$array1"; do
echo "$dirname"
done
echo
for dirname in "${array1[#]}"; do
echo "$dirname"
done
echo
echo '$array2'
echo "$array2"
echo
for dirname in "$array2"; do
echo "$dirname"
done
echo
for dirname in "${array2[#]}"; do
echo "$dirname"
done
rmdir temp/aaa
rmdir temp/bbb
rmdir temp/ccc
rmdir temp
Possible first line of every Bash script/session:
say() { for line in "${#}" ; do printf "%s\n" "${line}" ; done ; }
Use e.g.:
$ aa=( 7 -4 -e ) ; say "${aa[#]}"
7
-4
-e
May consider: echo interprets -e as option here
Single line looping,
declare -a listOfNames=('db_a' 'db_b' 'db_c')
for databaseName in ${listOfNames[#]}; do echo $databaseName; done;
you will get an output like this,
db_a
db_b
db_c
I loop through an array of my projects for a git pull update:
#!/bin/sh
projects="
web
ios
android
"
for project in $projects do
cd $HOME/develop/$project && git pull
end

Loading variables from a text file into bash script

Is it possible to load new lines from a text file to variables in bash?
Text file looks like?
EXAMPLEfoo
EXAMPLEbar
EXAMPLE1
EXAMPLE2
EXAMPLE3
EXAMPLE4
Variables become
$1 = EXAMPLEfoo
$2 = EXAMPLEbar
ans so on?
$ s=$(<file)
$ set -- $s
$ echo $1
EXAMPLEfoo
$ echo $2
EXAMPLEbar
$ echo $#
EXAMPLEfoo EXAMPLEbar EXAMPLE1 EXAMPLE2 EXAMPLE3 EXAMPLE4
I would improve the above by getting rid of temporary variable s:
$ set -- $(<file)
And if you have as input a file like this
variable1 = value
variable2 = value
You can use following construct to get named variables.
input=`cat filename|grep -v "^#"|grep "\c"`
set -- $input
while [ $1 ]
do
eval $1=$3
shift 3
done
cat somefile.txt| xargs bash_command.sh
bash_command.sh will receive these lines as arguments
saveIFS="$IFS"
IFS=$'\n'
array=($(<file))
IFS="$saveIFS"
echo ${array[0]} # output: EXAMPLEfoo
echo ${array[1]} # output: EXAMPLEbar
for i in "${array[#]}"; do echo "$i"; done # iterate over the array
Edit:
The loop in your pastebin has a few problems. Here it is as you've posted it:
for i in "${array[#]}"; do echo " "AD"$count = "$i""; $((count=count+1)); done
Here it is as it should be:
for i in "${array[#]}"; do declare AD$count="$i"; ((count=count+1)); done
or
for i in "${array[#]}"; do declare AD$count="$i"; ((count++)); done
But why not use the array directly? You could call it AD instead of array and instead of accessing a variable called "AD4" you'd access an array element "${AD[4]}".
echo "${AD[4]}"
if [[ ${AD[9]} == "EXAMPLE value" ]]; then do_something; fi
This can be done be with an array if you don't require these variables as inputs to a script. push() function lifted from the Advanced Scripting Guide
push() # Push item on stack.
{
if [ -z "$1" ] # Nothing to push?
then
return
fi
let "SP += 1" # Bump stack pointer.
stack[$SP]=$1
return
}
The contents of /tmp/test
[root#x~]# cat /tmp/test
EXAMPLEfoo
EXAMPLEbar
EXAMPLE1
EXAMPLE2
EXAMPLE3
EXAMPLE4
SP=0; for i in `cat /tmp/test`; do push $i ; done
Then
[root#x~]# echo ${stack[3]}
EXAMPLE1
None of the above will work, if your values are quoted with spaces.
However, not everythinf is lost.
Try this:
eval "$(VBoxManage showvminfo "$VMname" --details --machinereadable | egrep "^(name|UUID|CfgFile|VMState)")"
echo "$name {$UUID} $VMState ($VMStateChangeTime) CfgFile=$CfgFile"
P.S.
Nothing will ever work, if your names are quoted or contain dashes.
If you have something like that, as is the case with VBoxManage output ("IDE-1-0"="emptydrive" and so on), either egrep only specific values, as shown in my example, or silence the errors.
However, silencing erors is always dangerous. You never know, when the next value will have unquoted "*" in it, thus you must treat values loaded this way very careful, with all due precaution.

Resources