I'm trying to populate an associative array with the output of a command. I can do it without a command as:
$ declare -A x=( [first]=foo [second]=bar )
$ echo "${x[first]}, ${x[second]}"
foo, bar
and I can populate a non-associative array with command output as:
$ declare y=( $(echo 'foo bar') )
$ echo "${y[0]}, ${y[1]}"
foo, bar
but when I try to build on both of the above to create a statement that will populate an associative array from a command, I get the following error message:
$ declare -A z=( $(echo '[first]=foo [second]=bar') )
-bash: z: $(echo '[first]=foo [second]=bar'): must use subscript when assigning associative array
Why am I getting that error message and what is the correct syntax to populate an associative array with the output of a command? I am trying to avoid using eval for the usual reasons, do not want to use a temp file, and of course echo is just being used as an example of a command that produces the effect in question, the real command will be more complicated.
So, based on a couple of the answers below, it looks like it was just my quoting that was a problem:
$ declare -A z="( $(echo '[first]=foo [second]=bar') )"
$ echo "${z[first]}, ${z[second]}"
foo, bar
and with spaces in the indices and values:
$ declare -A z="( $(echo '[first field]="foo with space" [second]="space bar"') )"
$ echo "${z[first field]}, ${z[second]}"
foo with space, space bar
EDIT in response to a question in the comments about why the quotes are necessary (How do I populate a bash associative array with command output?) - I don't exactly know but maybe someone else can explain using the results of this script as reference (not expecting the specified indices to be used in the indexed arrays, they're just part of the strings being populated as the array values):
$ cat tst.sh
#!/bin/env bash
set -x
printf 'Indexed, no quotes\n'
declare -a w=( $(echo '[first]=foo [second]=bar') )
declare -p w
printf '\n---\n'
printf 'Indexed, with quotes\n'
declare -a x="( $(echo '[first]=foo [second]=bar') )"
declare -p x
printf '\n---\n'
printf 'Associative, no quotes\n'
declare -A y="( $(echo '[first]=foo [second]=bar') )"
declare -p y
printf '\n---\n'
printf 'Associative, with quotes\n'
declare -A z=( $(echo '[first]=foo [second]=bar') )
declare -p z
.
$ ./tst.sh
+ printf 'Indexed, no quotes\n'
Indexed, no quotes
+ w=($(echo '[first]=foo [second]=bar'))
++ echo '[first]=foo [second]=bar'
+ declare -a w
+ declare -p w
declare -a w=([0]="[first]=foo" [1]="[second]=bar")
+ printf '\n---\n'
---
+ printf 'Indexed, with quotes\n'
Indexed, with quotes
++ echo '[first]=foo [second]=bar'
+ declare -a 'x=( [first]=foo [second]=bar )'
+ declare -p x
declare -a x=([0]="bar")
+ printf '\n---\n'
---
+ printf 'Associative, no quotes\n'
Associative, no quotes
++ echo '[first]=foo [second]=bar'
+ declare -A 'y=( [first]=foo [second]=bar )'
+ declare -p y
declare -A y=([second]="bar" [first]="foo" )
+ printf '\n---\n'
---
+ printf 'Associative, with quotes\n'
Associative, with quotes
+ z=($(echo '[first]=foo [second]=bar'))
./tst.sh: line 24: z: $(echo '[first]=foo [second]=bar'): must use subscript when assigning associative array
+ declare -A z
+ declare -p z
declare -A z=()
Here is a traditional while loop approach to populate an associative array from a command's output:
while IFS= read -r; do
declare -A z+="( $REPLY )"
done < <(printf '[first]=foo [second]=bar\n[third]=baz\n')
# check output
$> echo "${z[first]}, ${z[second]}, ${z[third]}"
foo, bar, baz
# or declare -p
$> declare -p z
declare -A z='([third]="baz" [second]="bar" [first]="foo" )'
EDIT: Your original attempt will also work with proper quotes:
$> unset z
$> declare -A z="( $(echo '[first]=foo [second]=bar') )"
$> declare -p z
declare -A z='([second]="bar" [first]="foo" )'
I imagine this is somewhat brittle, but you can make the entire z=(...) assignment the result of a command substitution.
declare -A "$(echo z="($(echo '[first]=foo [second]=bar'))")"
Given that this works:
declare -A z=([first]=$(echo 'foo') [second]=$(echo 'bar'))
I'm guessing that Bash needs to see the associative array initialization list before doing any substitutions. So I don't see a way to avoid eval:
eval "declare -A z=($(echo '[first]=foo [second]=bar'))"
What is a "usual reason" to avoid eval?
Related
I have a string in a variable Var.
And the value looks like this:
Var="Key1:Val1~Key2:Val2~"
I just simply need this split by "~" and assigned to an array in KSH only
When I try Var2=$(echo $Var | sed $'s/~/\\n/g')
and check the size of Var2 array as follows:
ArrSize=${#Var2[#]}
I always get 1. I would have imagined that would be 2. Please Help
Assuming you want to use the x=( list of array items ) method of populating the array then you need to wrap the right side of the assignment in a pair of parens, eg:
$ Var2=( $( echo $Var | sed $'s/~/\\n/g' ) )
$ typeset -p Var2
typeset -a Var2=(Key1:Val1 Key2:Val2)
$ echo "${#Var2[#]}"
2
Other options that accomplish the same thing but reduce the overhead of subprocess calls:
here string:
$ Var2=( $(sed 's/~/ /g' <<< "${Var}") )
$ typeset -p Var2
typeset -a Var2=(Key1:Val1 Key2:Val2)
$ echo "${#Var2[#]}"
2
parameter substitution:
$ Var2=( ${Var//\~/ } )
$ typeset -p Var2
typeset -a Var2=(Key1:Val1 Key2:Val2)
$ echo "${#Var2[#]}"
2
NOTE: while ${var//~/ } works in ksh, other shells (eg, bash) require the ~ to be escaped (ie, \~); ksh appears to work with both - ~ and \~ = so I've updated the answer to include the escape
I have the following variable.
echo "|${VAR1}|"
which returns
|
ABC
XYZ|
How can I remove the empty lines, preserving line breaks and using parameter expansion? So that it would become
|ABC
XYZ|
p.s.: I know how to do it using pipe sed, but I would like to avoid the extra SED process:
VAR1=`echo "${VAR1}" | sed '/^\s*$/d'`
Remove the leading newlines, then replace any consecutive newlines with a single one.
#! /bin/bash
var='
ABC
XYZ'
expected='ABC
XYZ'
shopt -s extglob
var=${var##+($'\n')}
var=${var//+($'\n')/$'\n'}
[[ $var == $expected ]] && echo OK
read the lines of the variable into an array, and remove the empty elements
var1=$'\nABC\n\nXYZ'
mapfile -t arr <<<"$var1"
declare -p arr # => declare -a arr=([0]="" [1]="ABC" [2]="" [3]="XYZ")
for ((i = ${#arr[#]} - 1; i >= 0; i--)); do
[[ -z ${arr[i]} ]] && unset "arr[i]"
done
declare -p arr # => declare -a arr=([1]="ABC" [3]="XYZ")
(IFS=$'\n'; echo "|${arr[*]}|") # in a subshell for temporary IFS setting
|ABC
XYZ|
is it possible to create a loop that makes associative arrays in bash?
I would like something along these lines....
number_of_servers=10;
COUNTER=1
while [ $COUNTER -le ${number_of_servers} ]; do
declare -A "server_${COUNTER}"
COUNTER=$((COUNTER+1))
done
many thanks !
Your code already works:
$ for index in 1 2
> do
> declare -A "server_${index}"
> done
$ declare -p server_1
declare -A server_1
$ declare -p server_2
declare -A server_2
You can simplify it like #rici pointed out:
$ declare -A server_{3..4}
$ declare -p server_4
declare -A server_4
Or dynamically declare it:
$ number_of_servers=10
$ declare -A $(printf 'server_%d ' $(seq "$number_of_servers"))
$ declare -p server_10
declare -A server_10
I'm trying to split key value pairs (around an = sign) which I then use to edit a config file, using bash. But I need an alternative to the <<< syntax for IFS.
The below works on my host system, but when i log in to my ubuntu virtual machine through ssh I have the wrong bash version. Whatever I try, <<< fails. (I am definitely calling the right version of bash at the top of the file, using #!/bin/bash (and I've tried #!/bin/sh etc too)).
I know I can use IFS as follows on my host mac os x system:
var="word=hello"
IFS='=' read -a array <<<"$var"
echo ${array[0]} ${array[1]]}
#alternative -for calling through e.g. sh file.sh param=value
for var in "$#"
do
IFS='=' read -a array <<<"$var"
echo ${array[0]} ${array[1]]}
done
#alternative
IFS='=' read -ra array <<< "a=b"
declare -p array
echo ${array[0]} ${array[1]}
But this doesn't work on my vm.
I also know that I can should be able to switch the <<< syntax through backticks, $() or echo "$var" | ... but I can't get it to work - as follows:
#Fails
IFS='=' read -ra myarray -d '' <"$var"
echo ${array[0]} ${array[1]]}
#Fails
echo "$var" | IFS='=' read -a array
echo ${array[0]} ${array[1]]}
#fails
echo "a=b" | IFS='=' read -a array
declare -p array
echo ${array[0]} ${array[1]}
Grateful for any pointers as I'm really new to bash.
Your first failed attempt is because < and <<< are different operators. < opens the named file.
The second fails because read only sets the value of array in the subshell started by the pipe; that shell exits after the completion of the pipe, and array disappears with it.
The third fails for the same reason as the second; the declare that follows doesn't make any difference.
Your attempts have been confounded because you have to use the variable in the same sub-shell as read.
$ echo 'foo=bar' | { IFS='=' read -a array; echo ${array[0]}; }
foo
And if you want your variable durable (ie, outside the sub-shell scope):
$ var=$(echo 'foo=bar' | { IFS='=' read -a array; echo ${array[0]}; })
$ echo $var
foo
Clearly, it isn't pretty.
Update: If -a is missing, that suggests you're out of the land of arrays. You can try parameter substitution:
str='foo=bar'
var=${str%=*}
val=${str#*=}
And if that doesn't work, fall back to good ole cut:
str='foo=bar'
var=$(echo $str | cut -f 1 -d =)
val=$(echo $str | cut -f 2 -d =)
I hopefully would like to say I understand quotings used in BASH and their difference , " ", ' ', $' '.
I saw many shell scripts containing
IFS=$'\n'
but NO
IFS="\n"
It looks at least to me that there is no difference.
and in my environment both work correctly (for my understanding),
What difference is here? Is it just a custom?
They aren't the same.
IFS=$'\n' sets the value of IFS to a literal newline.
IFS="\n" sets the value of IFS to the string \n.
See?
$ IFS=$'\n'
$ declare -p IFS
declare -- IFS="
"
$ IFS="\n"
$ declare -p IFS
declare -- IFS="\\n"
$ IFS="\n" read a b c <<<$'anbncndn'
$ declare -p a b c
declare -- a="a"
declare -- b="b"
declare -- c="cndn"
$ IFS=$'\n' read a b c <<<$'anbncndn'
$ declare -p a b c
declare -- a="anbncndn"
declare -- b=""
declare -- c=""