3

I apologise for asking for asking such a trivial question which might have been answered here presumably a dozen times. But I cannot get my head around what effect is preventing Bash from contentedly accepting this assignment. I assume maybe some sort of non awareness of variable content in the forking parent which was assigned in the subshell child? But my tries with process substitution via < <(command_that_parses_value_to_be_assigned) remained futile.

Here's what puzzles me.

compound=$(lvs --noheadings --units m --nosuffix -o name,size vg_oracle|cut -d. -f1|awk '{printf"[%s]=%d ",$1,$2}')


echo $compound
[lv_home_oracle]=20480 [lv_home_oracle_app]=40960 [lv_home_oracle_swdepot]=40960 [lv_opt_crs]=40960


unset oralvs; declare -A oralvs

oralvs=($compound)
-bash: oralvs: $compound: must use subscript when assigning associative array

echo ${!oralvs[*]}

wheras assigning the array's contents via copy and paste works well, so that I cannot see a syntax error in my intended statement.

oralvs=([lv_home_oracle]=20480 [lv_home_oracle_app]=40960 [lv_home_oracle_swdepot]=40960 [lv_opt_crs]=40960)

echo ${!oralvs[*]}
lv_home_oracle_app lv_opt_crs lv_home_oracle_swdepot lv_home_oracle

 echo ${oralvs[*]}
40960 40960 40960 20480

Thank you for your kind attention and patience.

Was expecting that the array's compound keys-values assignment reading from variable or command substituion would work.

P.S. I think Bash: set associative array from variable answers my issue. The easiest for me to comprhende answer was the eval statement put in double quotes, which seems to work for me. e.g.

unset oralvs

echo ${!oralvs[*]}

eval "declare -A oralvs=($compound)"

echo ${!oralvs[*]}
lv_home_oracle_app lv_opt_crs lv_home_oracle_swdepot lv_home_oracle

echo ${oralvs[*]}
40960 40960 40960 20480

2
  • Try: declare -iA "oralvs=($(lvs --noheadings --units m --nosuffix -o name,size vg_oracle |awk -F'[.[:space:]]+' '{printf "[%s]=%s ",$2,$3;}'))" Commented May 7, 2024 at 15:35
  • 1
    But care, as declare -A "varname=($othervariable)" is a kind of eval, you have to be confident about your input! See my answer Commented May 11, 2024 at 12:40

6 Answers 6

3

Do the assignment and the declaration at the same time.

Given

compound='[lv_home_oracle]=20480 [lv_home_oracle_app]=40960 [lv_home_oracle_swdepot]=40960 [lv_opt_crs]=40960'

This fails, as you've discovered:

$ declare -A x; x=($compound)
$ declare -p x
declare -A x=(["[lv_home_oracle]=20480 [lv_home_oracle_app]=40960 [lv_home_oracle_swdepot]=40960 [lv_opt_crs]=40960"]="" )

But this is OK, without eval

$ declare -A "y=($compound)"
$ declare -p y
declare -A y=([lv_home_oracle_swdepot]="40960" [lv_home_oracle]="20480" [lv_opt_crs]="40960" [lv_home_oracle_app]="40960" )

This is bash version 5.2.26(1)-release

Sign up to request clarification or add additional context in comments.

1 Comment

2

The [...]=... is part of the syntax of an array assignment; it has to be present and recognized before parameter expansion occurs. Just like you can't do something like equals='='; x${equals}3 in place of x=3, you can't construct a dynamic array assignment like this (without eval, anyway).

Comments

1

Just assign the values, line by line.

tmp=$(lvs --noheadings --units m --nosuffix -o name,size vg_oracle | cut -d. -f1)
declare -A oralvs
while read -r a b; do
   oralvs["$a"]="$b"
done <<<"$tmp"

Comments

1

If I were you I would avoid eval for this, you're trying to solve a problem that you don't need to have as you don't need to pipe the output of a command to awk to create what looks like associate array instructions and then save that string in a scalar variable and then try to extract those instructions from the scalar variable again to populate an associative array. Its that saving in a scalar and then trying to extract again from the scalar that's causing your problem so just don't do that.

For example, if we assume the output of lvs --noheadings --units m --nosuffix -o name,size vg_oracle|cut -d. -f1 looks like this:

$ cat file
lv_home_oracle 20480
lv_home_oracle_app 40960
lv_home_oracle_swdepot 40960
lv_opt_crs 40960

then the natural approach would be to populate an associative array from that (obviously I'm using cat file here instead of lvs... just for demo purposes):

$ declare -A compound
$ while read -r name size; do compound[$name]=$size; done < <(cat file)

which gives us:

$ declare -p compound
declare -A compound=([lv_home_oracle_swdepot]="40960" [lv_home_oracle]="20480" [lv_opt_crs]="40960" [lv_home_oracle_app]="40960" )

and from there you can safely copy it into oralvs[] or do whatever you like with it.

1 Comment

@F.Hauri-GiveUpGitHub the OP wants to parse the output of a command, not the contents of a file so as I said in my answer "obviously I'm using cat file here instead of lvs... just for demo purposes".
1

Do it with declare:

#!/usr/bin/env bash

# Capture output in compound and check return code at the same time
if compound=$(
  lvs \
    --noheadings \
    --units m \
    --nosuffix \
    -o name,size vg_oracle |
    awk -F. '{split($1,f," ");printf"[%s]=%d ",f[1],f[2]}'
)
then
  # Use the declare statement with a string as de declaration argument
  declare -A "oralvs=($compound)"
fi

Comments

1

Using declare -A "varname=($compound)" is a kind of eval!!

If you have to create an associative variable from variable input, you have to be conscient that this syntax is a kind of eval:

compound='[lv_home_oracle]=20480 [test]=$(uptime)'
declare -A "y=($compound)"
declare -p y
declare -A y=([lv_home_oracle]="20480" [test]=" 12:38:47 up 250 days, 16:41,  1 
user,  load average: 1.36, 1.48, 1.33" )

How to populate an associative array without having to loop over each element?

Here is my solution. Using bash builtin printf and a temporary format string:

mapfile -t outlines < <(lvs --noheadings --units m -o name,size)
printf -v fmtStr '[%q]=%%q ' "${outlines[@]/%+( )[0-9]*}"
printf -v fmtStr "$fmtStr" "${outlines[@]/#* }"
declare -A "myLVM=(${fmtStr//\\ })"
declare -p myLVM
declare -A myLVM=([lv_home_oracle_swdepot]="40960.00m" [lv_home_oracle]="20480.0
0m" [lv_opt_crs]="40960.00m" [lv_home_oracle_app]="40960.00m" )

Then you could do a 2nd pass for dropping .00m:

printf -v fmtStr '[%q]=%%q ' "${!myLVM[@]}"
printf -v fmtStr "$fmtStr" "${myLVM[@]%.00m}"
declare -A "myLVM=($fmtStr)"
declare -p myLVM 
declare -A myLVM=([lv_home_oracle_swdepot]="40960" [lv_home_oracle]="20480" [lv_
opt_crs]="40960" [lv_home_oracle_app]="40960" )

Whatif, with a faked lvs:

lvs() {
    /sbin/lvs "$@"
    echo '  Test    $(uptime)'
}

Then:

mapfile -t outlines < <(lvs --noheadings --units m -o name,size)
printf -v fmtStr '[%q]=%%q ' "${outlines[@]/%+( )[0-9]*}"
printf -v fmtStr "$fmtStr" "${outlines[@]/#* }"
declare -A "myLVM=(${fmtStr//\\ })"
declare -p myLVM
declare -A myLVM=([lv_home_oracle_swdepot]="40960.00m" [lv_home_oracle]="20480.0
m" [lv_opt_crs]="40960.00m" ["test\$(uptime)"]="\$(uptime)" [lv_home_oracle_app
]="40960.00m" )
printf -v fmtStr '[%q]=%%q ' "${!myLVM[@]}"
printf -v fmtStr "$fmtStr" "${myLVM[@]%.00m}"
declare -A "myLVM=($fmtStr)"
declare -p myLVM 
declare -A myLVM=([lv_home_oracle_swdepot]="40960" [lv_home_oracle]="20480" [lv_
opt_crs]="40960" ["test\$(uptime)"]="\$(uptime)" [lv_home_oracle_app]="40960" )

Alternatively, playing with integer capabilities:

Instead of having to drop .00 string, you could ask for sizes in bytes, then convert into megabytes by simple fraction:

mapfile -t _lvs < <(lvs --noheadings --units b --nosuffix -o name,size)
printf -v fmt '[%q]=%%d/1024**2 ' ${_lvs[@]% *+([0-9])}
printf -v fmt "$fmt" ${_lvs[@]##* }
declare -iA "_LVS=($fmt)"
declare  -p _LVS
declare -Ai _LVS=([lv_home_oracle_swdepot]="40960" [lv_home_oracle]="20480" ["\$
(uptime)"]="0" [lv_opt_crs]="40960" [lv_home_oracle_app]="40960" [Test]="0" )
printf -v _lvs ' - %-22s %%11d\\n' ${!_LVS[@]}
printf "$_lvs" ${_LVS[@]}
 - lv_home_oracle_swdepot       40960
 - lv_home_oracle               20480
 - $(uptime)                        0
 - lv_opt_crs                   40960
 - lv_home_oracle_app           40960
 - Test                             0

1 Comment

Edit 2025 march: Adding integer mathematical fraction during variable assignment, without loop!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.