BASH associative array printing

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
13
down vote

favorite
3












Is there a way to print an entire array ([key]=value) without looping over all elements?



Assume I have created an array with some elements:



declare -A array
array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)


I can print back the entire array with



for i in "$!array[@]"
do
echo "$i=$array[$i]"
done


However, it seems bash already knows how to get all array elements in one "go" - both keys $!array[@] and values $array[@].



Is there a way to make bash print this info without the loop?



Edit:
typeset -p array does that!

However I can't remove both prefix and suffix in a single substitution:



a="$(typeset -p array)"
b="$a##*("
c="$b%% )*"


Is there a cleaner way to get/print only the key=value portion of the output?










share|improve this question

























    up vote
    13
    down vote

    favorite
    3












    Is there a way to print an entire array ([key]=value) without looping over all elements?



    Assume I have created an array with some elements:



    declare -A array
    array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)


    I can print back the entire array with



    for i in "$!array[@]"
    do
    echo "$i=$array[$i]"
    done


    However, it seems bash already knows how to get all array elements in one "go" - both keys $!array[@] and values $array[@].



    Is there a way to make bash print this info without the loop?



    Edit:
    typeset -p array does that!

    However I can't remove both prefix and suffix in a single substitution:



    a="$(typeset -p array)"
    b="$a##*("
    c="$b%% )*"


    Is there a cleaner way to get/print only the key=value portion of the output?










    share|improve this question























      up vote
      13
      down vote

      favorite
      3









      up vote
      13
      down vote

      favorite
      3






      3





      Is there a way to print an entire array ([key]=value) without looping over all elements?



      Assume I have created an array with some elements:



      declare -A array
      array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)


      I can print back the entire array with



      for i in "$!array[@]"
      do
      echo "$i=$array[$i]"
      done


      However, it seems bash already knows how to get all array elements in one "go" - both keys $!array[@] and values $array[@].



      Is there a way to make bash print this info without the loop?



      Edit:
      typeset -p array does that!

      However I can't remove both prefix and suffix in a single substitution:



      a="$(typeset -p array)"
      b="$a##*("
      c="$b%% )*"


      Is there a cleaner way to get/print only the key=value portion of the output?










      share|improve this question













      Is there a way to print an entire array ([key]=value) without looping over all elements?



      Assume I have created an array with some elements:



      declare -A array
      array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)


      I can print back the entire array with



      for i in "$!array[@]"
      do
      echo "$i=$array[$i]"
      done


      However, it seems bash already knows how to get all array elements in one "go" - both keys $!array[@] and values $array[@].



      Is there a way to make bash print this info without the loop?



      Edit:
      typeset -p array does that!

      However I can't remove both prefix and suffix in a single substitution:



      a="$(typeset -p array)"
      b="$a##*("
      c="$b%% )*"


      Is there a cleaner way to get/print only the key=value portion of the output?







      bash array associative-array






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked May 22 '17 at 16:13









      Dani_l

      3,052929




      3,052929




















          5 Answers
          5






          active

          oldest

          votes

















          up vote
          13
          down vote



          accepted










          I think you're asking two different things there.




          Is there a way to make bash print this info without the loop?




          Yes, but they are not as good as just using the loop.




          Is there a cleaner way to get/print only the key=value portion of the output?




          Yes, the for loop. It has the advantages that it doesn't require external programs, is straightforward, and makes it rather easy to control the exact output format without surprises.




          Any solution that tries to handle the output of declare -p (typeset -p)
          has to deal with a) the possibility of the variables themselves containing parenthesis or brackets, b) the quoting that declare -p has to add to make it's output valid input for the shell.



          For example, your expansion b="$a##*(" eats some of the values, if any key/value contains an opening parenthesis. This is because you used ##, which removes the longest prefix. Same for c="$b%% )*". Though you could of course match the boilerplate printed by declare more exactly, you'd still have a hard time if you didn't want all the quoting it does.



          This doesn't look very nice unless you need it.



          $ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
          $ declare -p array
          declare -A array='([def]=""foo bar"" [abc]="'''foobar'''" )'


          With the for loop, it's easier to choose the output format as you like:



          # without quoting
          $ for x in "$!array[@]"; do printf "[%s]=%sn" "$x" "$array[$x]" ; done
          [def]="foo bar"
          [abc]='foobar'

          # with quoting
          $ for x in "$!array[@]"; do printf "[%q]=%qn" "$x" "$array[$x]" ; done
          [def]="foo bar"
          [abc]='foobar'


          From there, it's also simple to change the output format otherwise (remove the brackets around the key, put all key/value pairs on a single line...). If you need quoting for something other than the shell itself, you'll still need to do it by yourself, but at least you have the raw data to work on. (If you have newlines in the keys or values, you are probably going to need some quoting.)



          With a current Bash (4.4, I think), you could also use printf "[%s]=%s" "$x@Q" "$array[$x]@Q" instead of printf "%q=%q". It produces a somewhat nicer quoted format, but is of course a bit more work to remember to write. (And it quotes the corner case of @ as array key, which %q doesn't quote.)



          If the for loop seems too weary to write, save it a function somewhere (without quoting here):



          printarr() declare -n __p="$1"; for k in "$!__p[@]"; do printf "%s=%sn" "$k" "$__p[$k]" ; done ; 


          And then just use that:



          $ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
          $ printarr a
          a=123
          b=foo bar
          c=(blah)


          Works with indexed arrays, too:



          $ b=(abba acdc)
          $ printarr b
          0=abba
          1=acdc





          share|improve this answer






















          • Note that the output of your printf ...%q... variant is not suitable for reinput to the shell if the array has a @ key as %q doesn't quote it and a=([@]=value) is a syntax error in bash.
            – Stéphane Chazelas
            May 23 '17 at 7:02











          • @StéphaneChazelas, apparently. "$x@Q" quotes that too, since it quotes all strings (and looks nicer). added a note about using that.
            – ilkkachu
            May 23 '17 at 7:26










          • Yes, copied from mksh. Another operator of yet a different shape which cannot be combined with most others. Again, see zsh with its variable expansion flags (that again predates bash's by decades and with which you can choose the quoting style: $(q)var, $(qq)var...) for a better design. bash has the same issue as mksh in that it doesn't quote the empty string (not an issue here as anyway bash doesn't support empty keys). Also, when using quoting styles other than single quote ($var@Q resorts to $'...' for some values) it's important that the code be reinput in the same locale.
            – Stéphane Chazelas
            May 23 '17 at 9:20










          • @StéphaneChazelas, I think you mean an unset value, not an empty string? (x=; echo "$x@Q" does give '', unset x; echo "$x@Q" gives nothing.) Bash's @Q seems to prefer $'n' over a literal newline, which may actually be good in some situations (but I can't tell what others prefer). Of course having a choice there wouldn't be bad.
            – ilkkachu
            May 23 '17 at 9:36










          • Oh yes sorry, I hadn't realised that. That's a difference from mksh then. The $'...' syntax is a potential problem in things like LC_ALL=zh_HK.big5hkscs bash -c 'a=$'''nu3b1'''; printf "%sn" "$a@Q"' which outputs $'n<0xa3><0x5c>' and 0x5c alone is backslash so you'd have a problem if that quote was interpreted in a different locale.
            – Stéphane Chazelas
            May 23 '17 at 10:07


















          up vote
          9
          down vote













          declare -p array
          declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'


          2 fork



          Maybe this:



          printf "%sn" "$!array[@]"
          a2
          a1
          f50
          zz
          b1

          printf "%sn" "$array[@]"
          2
          1
          abcd
          Hello World
          bbb

          printf "%sn" "$!array[@]" "$array[@]" | pr -2t
          a2 2
          a1 1
          f50 abcd
          zz Hello World
          b1 bbb


          3 forks



          or this:



          paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]")
          a2=2
          a1=1
          f50=abcd
          zz=Hello World
          b1=bbb


          No fork



          to be compared to



          for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done
          a2=2
          a1=1
          f50=abcd
          zz=Hello World
          b1=bbb


          Execution times comparission



          As last syntax don't use fork, they could be quicker:



          time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
          5 11 76
          real 0m0.005s
          user 0m0.000s
          sys 0m0.000s

          time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
          5 6 41
          real 0m0.008s
          user 0m0.000s
          sys 0m0.000s

          time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
          5 6 41
          real 0m0.002s
          user 0m0.000s
          sys 0m0.001s


          But this affirmation doesn't stay true if the array becomes big; if reducing forks is efficient for small process, using dedicated tools is more efficient for bigger process.



          for i in a..za..za..z;do array[$i]=$RANDOM;done


          time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
          17581 35163 292941
          real 0m0.150s
          user 0m0.124s
          sys 0m0.036s

          time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
          17581 17582 169875
          real 0m0.140s
          user 0m0.000s
          sys 0m0.004s

          time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
          17581 17582 169875
          real 0m0.312s
          user 0m0.268s
          sys 0m0.076s


          Remark



          As both (forked) solutions use alignment, none of them will work if any variable contains a newline. In this case, the only way is a for loop.






          share|improve this answer






















          • While looking clever, both ways are less efficient than a for. Which is a shame, really.
            – Satō Katsura
            May 22 '17 at 16:50










          • @SatoKatsura I agree, but if slower, syntax using pr is shorter... I'm not sure about pr syntax stay slower, even with big arrays!
            – F. Hauri
            May 22 '17 at 17:14







          • 2




            @MiniMax Because it doesn't produce the correct result (same elements, wrong order). You'd need to zip arrays $!array[@] and $array[@] first for that to work.
            – Satō Katsura
            May 22 '17 at 19:30







          • 1




            That last snippet with paste is longer than the for loop in the question written on one line for i in "$!array[@]"; do echo "$i=$array[$i]" ; done, but requires two subshells and an external program. How is that neater? The solution with pr also breaks if there are many elements, as it tries to paginate the output. You'd need to use something like | pr -2t -l"$#array[@]" which is starting to get hard to remember compared to the simple loop, and again, is longer than it.
            – ilkkachu
            May 22 '17 at 21:45






          • 1




            In bash, cmd1 | cmd2 means 2 forks, even if cmd1 or cmd2 or both are builtin.
            – Stéphane Chazelas
            May 23 '17 at 6:28

















          up vote
          2
          down vote













          If you're looking for a shell with better associative array support, try zsh.



          In zsh (where associative arrays were added in 1998, compared to 1993 for ksh93 and 2009 for bash), $var or $(v)var expands to the (non-empty) values of the hash, $(k)var to the (non-empty) keys (in the same order), and $(kv)var to both keys and values.



          To preserve the empty values, like for arrays, you need to quote and use the @ flag.



          So to print the keys and values, it's just a matter of



          printf '%s => %sn' "$(@kv)var"


          Though to account for a possibly empty hash, you should do:



          (($#var)) && printf '%s => %sn' "$(@kv)var"


          Also note that zsh uses a much more sensible and useful array definition syntax than ksh93's (copied by bash):



          typeset -A var
          var=(k1 v1 k2 v2 '' empty '*' star)


          Which makes it a lot easier to copy or merge associative arrays:



          var2=("$(@kv)var1")
          var3+=("$(@kv)var2")
          var4=("$@kv)var4" "$(@kv)var5")


          (you can't easily copy a hash without a loop with bash, and note that bash currently doesn't support empty keys or key/values with NUL bytes).



          See also zsh array zipping features which you'll typically need to work with associative arrays:



          keys=($(<keys.txt)) values=($(<values.txt))
          hash=($keys:^values)





          share|improve this answer



























            up vote
            1
            down vote













            Since typeset does what you want why not just edit its output?



            typeset -p array | sed s/^.*(// | tr -d ")'"" | tr "[" "n" | sed s/]=/' = '/


            gives



            a2 = 2 
            a1 = 1
            b1 = bbb


            Where



            array='([a2]="2" [a1]="1" [b1]="bbb" )'


            Verbose but it's pretty easy to see how the formatting works: just execute the pipeline with progressively more of the sed and tr commands. Modify them to suit pretty printing tastes.






            share|improve this answer






















            • That kind of a pipeline is bound to fail the moment some of the keys or values of the array contain any of the characters you're replacing, like parenthesis, brackets or quotes. And a pipeline of seds and tr's isn't even much simpler than a for loop with printf.
              – ilkkachu
              May 22 '17 at 21:19










            • Also, you do know that tr translate character-by-character, it doesn't match strings? tr "]=" " =" changes "]" to a space and an = to an =, regardless of position. So you could probably just combine all three tr's to one.
              – ilkkachu
              May 22 '17 at 21:22











            • Very true about some of the non-alphanumeric characters gumming this up. However anything that has to deal them gets an order of magnitude more complex and less readable so unless there's a really good reason to have them in your data feed and that's stated in the question I assume they're filtered out before we got here. Should always have your explicit caveat tho. I find these pipelines to be simpler, for example and debugging purposes, than a printf glob that either works perfectly or blows up in your face. Here you make one simple change per element, test it, then add 1 more.
              – Nadreck
              May 23 '17 at 1:10










            • My bad! Got my _tr_s and _sed_s totally mixed up! Fixed in the latest edit.
              – Nadreck
              May 23 '17 at 1:12

















            up vote
            0
            down vote













            One more option is to list all the variables and grep for the one you want.



            set | grep -e '^aa='



            I use this for debugging. I doubt that it is very performant since it lists all the variables.



            If you were doing this often you could make it a function like this:



            aap() grep -e "^$1=";



            Unfortunately when we check performance using time:




            $ time aap aa
            aa=([0]="abc")
            .
            real 0m0.014s
            user 0m0.003s
            sys 0m0.006s



            Therefore, if you were doing this very often, you'd want @F.Hauri's NO FORKS version because it is so much faster.






            share|improve this answer




















              Your Answer







              StackExchange.ready(function()
              var channelOptions =
              tags: "".split(" "),
              id: "106"
              ;
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function()
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled)
              StackExchange.using("snippets", function()
              createEditor();
              );

              else
              createEditor();

              );

              function createEditor()
              StackExchange.prepareEditor(
              heartbeatType: 'answer',
              convertImagesToLinks: false,
              noModals: false,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: null,
              bindNavPrevention: true,
              postfix: "",
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              );



              );













               

              draft saved


              draft discarded


















              StackExchange.ready(
              function ()
              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f366581%2fbash-associative-array-printing%23new-answer', 'question_page');

              );

              Post as a guest






























              5 Answers
              5






              active

              oldest

              votes








              5 Answers
              5






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes








              up vote
              13
              down vote



              accepted










              I think you're asking two different things there.




              Is there a way to make bash print this info without the loop?




              Yes, but they are not as good as just using the loop.




              Is there a cleaner way to get/print only the key=value portion of the output?




              Yes, the for loop. It has the advantages that it doesn't require external programs, is straightforward, and makes it rather easy to control the exact output format without surprises.




              Any solution that tries to handle the output of declare -p (typeset -p)
              has to deal with a) the possibility of the variables themselves containing parenthesis or brackets, b) the quoting that declare -p has to add to make it's output valid input for the shell.



              For example, your expansion b="$a##*(" eats some of the values, if any key/value contains an opening parenthesis. This is because you used ##, which removes the longest prefix. Same for c="$b%% )*". Though you could of course match the boilerplate printed by declare more exactly, you'd still have a hard time if you didn't want all the quoting it does.



              This doesn't look very nice unless you need it.



              $ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
              $ declare -p array
              declare -A array='([def]=""foo bar"" [abc]="'''foobar'''" )'


              With the for loop, it's easier to choose the output format as you like:



              # without quoting
              $ for x in "$!array[@]"; do printf "[%s]=%sn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'

              # with quoting
              $ for x in "$!array[@]"; do printf "[%q]=%qn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'


              From there, it's also simple to change the output format otherwise (remove the brackets around the key, put all key/value pairs on a single line...). If you need quoting for something other than the shell itself, you'll still need to do it by yourself, but at least you have the raw data to work on. (If you have newlines in the keys or values, you are probably going to need some quoting.)



              With a current Bash (4.4, I think), you could also use printf "[%s]=%s" "$x@Q" "$array[$x]@Q" instead of printf "%q=%q". It produces a somewhat nicer quoted format, but is of course a bit more work to remember to write. (And it quotes the corner case of @ as array key, which %q doesn't quote.)



              If the for loop seems too weary to write, save it a function somewhere (without quoting here):



              printarr() declare -n __p="$1"; for k in "$!__p[@]"; do printf "%s=%sn" "$k" "$__p[$k]" ; done ; 


              And then just use that:



              $ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
              $ printarr a
              a=123
              b=foo bar
              c=(blah)


              Works with indexed arrays, too:



              $ b=(abba acdc)
              $ printarr b
              0=abba
              1=acdc





              share|improve this answer






















              • Note that the output of your printf ...%q... variant is not suitable for reinput to the shell if the array has a @ key as %q doesn't quote it and a=([@]=value) is a syntax error in bash.
                – Stéphane Chazelas
                May 23 '17 at 7:02











              • @StéphaneChazelas, apparently. "$x@Q" quotes that too, since it quotes all strings (and looks nicer). added a note about using that.
                – ilkkachu
                May 23 '17 at 7:26










              • Yes, copied from mksh. Another operator of yet a different shape which cannot be combined with most others. Again, see zsh with its variable expansion flags (that again predates bash's by decades and with which you can choose the quoting style: $(q)var, $(qq)var...) for a better design. bash has the same issue as mksh in that it doesn't quote the empty string (not an issue here as anyway bash doesn't support empty keys). Also, when using quoting styles other than single quote ($var@Q resorts to $'...' for some values) it's important that the code be reinput in the same locale.
                – Stéphane Chazelas
                May 23 '17 at 9:20










              • @StéphaneChazelas, I think you mean an unset value, not an empty string? (x=; echo "$x@Q" does give '', unset x; echo "$x@Q" gives nothing.) Bash's @Q seems to prefer $'n' over a literal newline, which may actually be good in some situations (but I can't tell what others prefer). Of course having a choice there wouldn't be bad.
                – ilkkachu
                May 23 '17 at 9:36










              • Oh yes sorry, I hadn't realised that. That's a difference from mksh then. The $'...' syntax is a potential problem in things like LC_ALL=zh_HK.big5hkscs bash -c 'a=$'''nu3b1'''; printf "%sn" "$a@Q"' which outputs $'n<0xa3><0x5c>' and 0x5c alone is backslash so you'd have a problem if that quote was interpreted in a different locale.
                – Stéphane Chazelas
                May 23 '17 at 10:07















              up vote
              13
              down vote



              accepted










              I think you're asking two different things there.




              Is there a way to make bash print this info without the loop?




              Yes, but they are not as good as just using the loop.




              Is there a cleaner way to get/print only the key=value portion of the output?




              Yes, the for loop. It has the advantages that it doesn't require external programs, is straightforward, and makes it rather easy to control the exact output format without surprises.




              Any solution that tries to handle the output of declare -p (typeset -p)
              has to deal with a) the possibility of the variables themselves containing parenthesis or brackets, b) the quoting that declare -p has to add to make it's output valid input for the shell.



              For example, your expansion b="$a##*(" eats some of the values, if any key/value contains an opening parenthesis. This is because you used ##, which removes the longest prefix. Same for c="$b%% )*". Though you could of course match the boilerplate printed by declare more exactly, you'd still have a hard time if you didn't want all the quoting it does.



              This doesn't look very nice unless you need it.



              $ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
              $ declare -p array
              declare -A array='([def]=""foo bar"" [abc]="'''foobar'''" )'


              With the for loop, it's easier to choose the output format as you like:



              # without quoting
              $ for x in "$!array[@]"; do printf "[%s]=%sn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'

              # with quoting
              $ for x in "$!array[@]"; do printf "[%q]=%qn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'


              From there, it's also simple to change the output format otherwise (remove the brackets around the key, put all key/value pairs on a single line...). If you need quoting for something other than the shell itself, you'll still need to do it by yourself, but at least you have the raw data to work on. (If you have newlines in the keys or values, you are probably going to need some quoting.)



              With a current Bash (4.4, I think), you could also use printf "[%s]=%s" "$x@Q" "$array[$x]@Q" instead of printf "%q=%q". It produces a somewhat nicer quoted format, but is of course a bit more work to remember to write. (And it quotes the corner case of @ as array key, which %q doesn't quote.)



              If the for loop seems too weary to write, save it a function somewhere (without quoting here):



              printarr() declare -n __p="$1"; for k in "$!__p[@]"; do printf "%s=%sn" "$k" "$__p[$k]" ; done ; 


              And then just use that:



              $ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
              $ printarr a
              a=123
              b=foo bar
              c=(blah)


              Works with indexed arrays, too:



              $ b=(abba acdc)
              $ printarr b
              0=abba
              1=acdc





              share|improve this answer






















              • Note that the output of your printf ...%q... variant is not suitable for reinput to the shell if the array has a @ key as %q doesn't quote it and a=([@]=value) is a syntax error in bash.
                – Stéphane Chazelas
                May 23 '17 at 7:02











              • @StéphaneChazelas, apparently. "$x@Q" quotes that too, since it quotes all strings (and looks nicer). added a note about using that.
                – ilkkachu
                May 23 '17 at 7:26










              • Yes, copied from mksh. Another operator of yet a different shape which cannot be combined with most others. Again, see zsh with its variable expansion flags (that again predates bash's by decades and with which you can choose the quoting style: $(q)var, $(qq)var...) for a better design. bash has the same issue as mksh in that it doesn't quote the empty string (not an issue here as anyway bash doesn't support empty keys). Also, when using quoting styles other than single quote ($var@Q resorts to $'...' for some values) it's important that the code be reinput in the same locale.
                – Stéphane Chazelas
                May 23 '17 at 9:20










              • @StéphaneChazelas, I think you mean an unset value, not an empty string? (x=; echo "$x@Q" does give '', unset x; echo "$x@Q" gives nothing.) Bash's @Q seems to prefer $'n' over a literal newline, which may actually be good in some situations (but I can't tell what others prefer). Of course having a choice there wouldn't be bad.
                – ilkkachu
                May 23 '17 at 9:36










              • Oh yes sorry, I hadn't realised that. That's a difference from mksh then. The $'...' syntax is a potential problem in things like LC_ALL=zh_HK.big5hkscs bash -c 'a=$'''nu3b1'''; printf "%sn" "$a@Q"' which outputs $'n<0xa3><0x5c>' and 0x5c alone is backslash so you'd have a problem if that quote was interpreted in a different locale.
                – Stéphane Chazelas
                May 23 '17 at 10:07













              up vote
              13
              down vote



              accepted







              up vote
              13
              down vote



              accepted






              I think you're asking two different things there.




              Is there a way to make bash print this info without the loop?




              Yes, but they are not as good as just using the loop.




              Is there a cleaner way to get/print only the key=value portion of the output?




              Yes, the for loop. It has the advantages that it doesn't require external programs, is straightforward, and makes it rather easy to control the exact output format without surprises.




              Any solution that tries to handle the output of declare -p (typeset -p)
              has to deal with a) the possibility of the variables themselves containing parenthesis or brackets, b) the quoting that declare -p has to add to make it's output valid input for the shell.



              For example, your expansion b="$a##*(" eats some of the values, if any key/value contains an opening parenthesis. This is because you used ##, which removes the longest prefix. Same for c="$b%% )*". Though you could of course match the boilerplate printed by declare more exactly, you'd still have a hard time if you didn't want all the quoting it does.



              This doesn't look very nice unless you need it.



              $ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
              $ declare -p array
              declare -A array='([def]=""foo bar"" [abc]="'''foobar'''" )'


              With the for loop, it's easier to choose the output format as you like:



              # without quoting
              $ for x in "$!array[@]"; do printf "[%s]=%sn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'

              # with quoting
              $ for x in "$!array[@]"; do printf "[%q]=%qn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'


              From there, it's also simple to change the output format otherwise (remove the brackets around the key, put all key/value pairs on a single line...). If you need quoting for something other than the shell itself, you'll still need to do it by yourself, but at least you have the raw data to work on. (If you have newlines in the keys or values, you are probably going to need some quoting.)



              With a current Bash (4.4, I think), you could also use printf "[%s]=%s" "$x@Q" "$array[$x]@Q" instead of printf "%q=%q". It produces a somewhat nicer quoted format, but is of course a bit more work to remember to write. (And it quotes the corner case of @ as array key, which %q doesn't quote.)



              If the for loop seems too weary to write, save it a function somewhere (without quoting here):



              printarr() declare -n __p="$1"; for k in "$!__p[@]"; do printf "%s=%sn" "$k" "$__p[$k]" ; done ; 


              And then just use that:



              $ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
              $ printarr a
              a=123
              b=foo bar
              c=(blah)


              Works with indexed arrays, too:



              $ b=(abba acdc)
              $ printarr b
              0=abba
              1=acdc





              share|improve this answer














              I think you're asking two different things there.




              Is there a way to make bash print this info without the loop?




              Yes, but they are not as good as just using the loop.




              Is there a cleaner way to get/print only the key=value portion of the output?




              Yes, the for loop. It has the advantages that it doesn't require external programs, is straightforward, and makes it rather easy to control the exact output format without surprises.




              Any solution that tries to handle the output of declare -p (typeset -p)
              has to deal with a) the possibility of the variables themselves containing parenthesis or brackets, b) the quoting that declare -p has to add to make it's output valid input for the shell.



              For example, your expansion b="$a##*(" eats some of the values, if any key/value contains an opening parenthesis. This is because you used ##, which removes the longest prefix. Same for c="$b%% )*". Though you could of course match the boilerplate printed by declare more exactly, you'd still have a hard time if you didn't want all the quoting it does.



              This doesn't look very nice unless you need it.



              $ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
              $ declare -p array
              declare -A array='([def]=""foo bar"" [abc]="'''foobar'''" )'


              With the for loop, it's easier to choose the output format as you like:



              # without quoting
              $ for x in "$!array[@]"; do printf "[%s]=%sn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'

              # with quoting
              $ for x in "$!array[@]"; do printf "[%q]=%qn" "$x" "$array[$x]" ; done
              [def]="foo bar"
              [abc]='foobar'


              From there, it's also simple to change the output format otherwise (remove the brackets around the key, put all key/value pairs on a single line...). If you need quoting for something other than the shell itself, you'll still need to do it by yourself, but at least you have the raw data to work on. (If you have newlines in the keys or values, you are probably going to need some quoting.)



              With a current Bash (4.4, I think), you could also use printf "[%s]=%s" "$x@Q" "$array[$x]@Q" instead of printf "%q=%q". It produces a somewhat nicer quoted format, but is of course a bit more work to remember to write. (And it quotes the corner case of @ as array key, which %q doesn't quote.)



              If the for loop seems too weary to write, save it a function somewhere (without quoting here):



              printarr() declare -n __p="$1"; for k in "$!__p[@]"; do printf "%s=%sn" "$k" "$__p[$k]" ; done ; 


              And then just use that:



              $ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
              $ printarr a
              a=123
              b=foo bar
              c=(blah)


              Works with indexed arrays, too:



              $ b=(abba acdc)
              $ printarr b
              0=abba
              1=acdc






              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited May 23 '17 at 7:24

























              answered May 22 '17 at 22:18









              ilkkachu

              52.8k679145




              52.8k679145











              • Note that the output of your printf ...%q... variant is not suitable for reinput to the shell if the array has a @ key as %q doesn't quote it and a=([@]=value) is a syntax error in bash.
                – Stéphane Chazelas
                May 23 '17 at 7:02











              • @StéphaneChazelas, apparently. "$x@Q" quotes that too, since it quotes all strings (and looks nicer). added a note about using that.
                – ilkkachu
                May 23 '17 at 7:26










              • Yes, copied from mksh. Another operator of yet a different shape which cannot be combined with most others. Again, see zsh with its variable expansion flags (that again predates bash's by decades and with which you can choose the quoting style: $(q)var, $(qq)var...) for a better design. bash has the same issue as mksh in that it doesn't quote the empty string (not an issue here as anyway bash doesn't support empty keys). Also, when using quoting styles other than single quote ($var@Q resorts to $'...' for some values) it's important that the code be reinput in the same locale.
                – Stéphane Chazelas
                May 23 '17 at 9:20










              • @StéphaneChazelas, I think you mean an unset value, not an empty string? (x=; echo "$x@Q" does give '', unset x; echo "$x@Q" gives nothing.) Bash's @Q seems to prefer $'n' over a literal newline, which may actually be good in some situations (but I can't tell what others prefer). Of course having a choice there wouldn't be bad.
                – ilkkachu
                May 23 '17 at 9:36










              • Oh yes sorry, I hadn't realised that. That's a difference from mksh then. The $'...' syntax is a potential problem in things like LC_ALL=zh_HK.big5hkscs bash -c 'a=$'''nu3b1'''; printf "%sn" "$a@Q"' which outputs $'n<0xa3><0x5c>' and 0x5c alone is backslash so you'd have a problem if that quote was interpreted in a different locale.
                – Stéphane Chazelas
                May 23 '17 at 10:07

















              • Note that the output of your printf ...%q... variant is not suitable for reinput to the shell if the array has a @ key as %q doesn't quote it and a=([@]=value) is a syntax error in bash.
                – Stéphane Chazelas
                May 23 '17 at 7:02











              • @StéphaneChazelas, apparently. "$x@Q" quotes that too, since it quotes all strings (and looks nicer). added a note about using that.
                – ilkkachu
                May 23 '17 at 7:26










              • Yes, copied from mksh. Another operator of yet a different shape which cannot be combined with most others. Again, see zsh with its variable expansion flags (that again predates bash's by decades and with which you can choose the quoting style: $(q)var, $(qq)var...) for a better design. bash has the same issue as mksh in that it doesn't quote the empty string (not an issue here as anyway bash doesn't support empty keys). Also, when using quoting styles other than single quote ($var@Q resorts to $'...' for some values) it's important that the code be reinput in the same locale.
                – Stéphane Chazelas
                May 23 '17 at 9:20










              • @StéphaneChazelas, I think you mean an unset value, not an empty string? (x=; echo "$x@Q" does give '', unset x; echo "$x@Q" gives nothing.) Bash's @Q seems to prefer $'n' over a literal newline, which may actually be good in some situations (but I can't tell what others prefer). Of course having a choice there wouldn't be bad.
                – ilkkachu
                May 23 '17 at 9:36










              • Oh yes sorry, I hadn't realised that. That's a difference from mksh then. The $'...' syntax is a potential problem in things like LC_ALL=zh_HK.big5hkscs bash -c 'a=$'''nu3b1'''; printf "%sn" "$a@Q"' which outputs $'n<0xa3><0x5c>' and 0x5c alone is backslash so you'd have a problem if that quote was interpreted in a different locale.
                – Stéphane Chazelas
                May 23 '17 at 10:07
















              Note that the output of your printf ...%q... variant is not suitable for reinput to the shell if the array has a @ key as %q doesn't quote it and a=([@]=value) is a syntax error in bash.
              – Stéphane Chazelas
              May 23 '17 at 7:02





              Note that the output of your printf ...%q... variant is not suitable for reinput to the shell if the array has a @ key as %q doesn't quote it and a=([@]=value) is a syntax error in bash.
              – Stéphane Chazelas
              May 23 '17 at 7:02













              @StéphaneChazelas, apparently. "$x@Q" quotes that too, since it quotes all strings (and looks nicer). added a note about using that.
              – ilkkachu
              May 23 '17 at 7:26




              @StéphaneChazelas, apparently. "$x@Q" quotes that too, since it quotes all strings (and looks nicer). added a note about using that.
              – ilkkachu
              May 23 '17 at 7:26












              Yes, copied from mksh. Another operator of yet a different shape which cannot be combined with most others. Again, see zsh with its variable expansion flags (that again predates bash's by decades and with which you can choose the quoting style: $(q)var, $(qq)var...) for a better design. bash has the same issue as mksh in that it doesn't quote the empty string (not an issue here as anyway bash doesn't support empty keys). Also, when using quoting styles other than single quote ($var@Q resorts to $'...' for some values) it's important that the code be reinput in the same locale.
              – Stéphane Chazelas
              May 23 '17 at 9:20




              Yes, copied from mksh. Another operator of yet a different shape which cannot be combined with most others. Again, see zsh with its variable expansion flags (that again predates bash's by decades and with which you can choose the quoting style: $(q)var, $(qq)var...) for a better design. bash has the same issue as mksh in that it doesn't quote the empty string (not an issue here as anyway bash doesn't support empty keys). Also, when using quoting styles other than single quote ($var@Q resorts to $'...' for some values) it's important that the code be reinput in the same locale.
              – Stéphane Chazelas
              May 23 '17 at 9:20












              @StéphaneChazelas, I think you mean an unset value, not an empty string? (x=; echo "$x@Q" does give '', unset x; echo "$x@Q" gives nothing.) Bash's @Q seems to prefer $'n' over a literal newline, which may actually be good in some situations (but I can't tell what others prefer). Of course having a choice there wouldn't be bad.
              – ilkkachu
              May 23 '17 at 9:36




              @StéphaneChazelas, I think you mean an unset value, not an empty string? (x=; echo "$x@Q" does give '', unset x; echo "$x@Q" gives nothing.) Bash's @Q seems to prefer $'n' over a literal newline, which may actually be good in some situations (but I can't tell what others prefer). Of course having a choice there wouldn't be bad.
              – ilkkachu
              May 23 '17 at 9:36












              Oh yes sorry, I hadn't realised that. That's a difference from mksh then. The $'...' syntax is a potential problem in things like LC_ALL=zh_HK.big5hkscs bash -c 'a=$'''nu3b1'''; printf "%sn" "$a@Q"' which outputs $'n<0xa3><0x5c>' and 0x5c alone is backslash so you'd have a problem if that quote was interpreted in a different locale.
              – Stéphane Chazelas
              May 23 '17 at 10:07





              Oh yes sorry, I hadn't realised that. That's a difference from mksh then. The $'...' syntax is a potential problem in things like LC_ALL=zh_HK.big5hkscs bash -c 'a=$'''nu3b1'''; printf "%sn" "$a@Q"' which outputs $'n<0xa3><0x5c>' and 0x5c alone is backslash so you'd have a problem if that quote was interpreted in a different locale.
              – Stéphane Chazelas
              May 23 '17 at 10:07













              up vote
              9
              down vote













              declare -p array
              declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'


              2 fork



              Maybe this:



              printf "%sn" "$!array[@]"
              a2
              a1
              f50
              zz
              b1

              printf "%sn" "$array[@]"
              2
              1
              abcd
              Hello World
              bbb

              printf "%sn" "$!array[@]" "$array[@]" | pr -2t
              a2 2
              a1 1
              f50 abcd
              zz Hello World
              b1 bbb


              3 forks



              or this:



              paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]")
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              No fork



              to be compared to



              for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              Execution times comparission



              As last syntax don't use fork, they could be quicker:



              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              5 11 76
              real 0m0.005s
              user 0m0.000s
              sys 0m0.000s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              5 6 41
              real 0m0.008s
              user 0m0.000s
              sys 0m0.000s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              5 6 41
              real 0m0.002s
              user 0m0.000s
              sys 0m0.001s


              But this affirmation doesn't stay true if the array becomes big; if reducing forks is efficient for small process, using dedicated tools is more efficient for bigger process.



              for i in a..za..za..z;do array[$i]=$RANDOM;done


              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              17581 35163 292941
              real 0m0.150s
              user 0m0.124s
              sys 0m0.036s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              17581 17582 169875
              real 0m0.140s
              user 0m0.000s
              sys 0m0.004s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              17581 17582 169875
              real 0m0.312s
              user 0m0.268s
              sys 0m0.076s


              Remark



              As both (forked) solutions use alignment, none of them will work if any variable contains a newline. In this case, the only way is a for loop.






              share|improve this answer






















              • While looking clever, both ways are less efficient than a for. Which is a shame, really.
                – Satō Katsura
                May 22 '17 at 16:50










              • @SatoKatsura I agree, but if slower, syntax using pr is shorter... I'm not sure about pr syntax stay slower, even with big arrays!
                – F. Hauri
                May 22 '17 at 17:14







              • 2




                @MiniMax Because it doesn't produce the correct result (same elements, wrong order). You'd need to zip arrays $!array[@] and $array[@] first for that to work.
                – Satō Katsura
                May 22 '17 at 19:30







              • 1




                That last snippet with paste is longer than the for loop in the question written on one line for i in "$!array[@]"; do echo "$i=$array[$i]" ; done, but requires two subshells and an external program. How is that neater? The solution with pr also breaks if there are many elements, as it tries to paginate the output. You'd need to use something like | pr -2t -l"$#array[@]" which is starting to get hard to remember compared to the simple loop, and again, is longer than it.
                – ilkkachu
                May 22 '17 at 21:45






              • 1




                In bash, cmd1 | cmd2 means 2 forks, even if cmd1 or cmd2 or both are builtin.
                – Stéphane Chazelas
                May 23 '17 at 6:28














              up vote
              9
              down vote













              declare -p array
              declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'


              2 fork



              Maybe this:



              printf "%sn" "$!array[@]"
              a2
              a1
              f50
              zz
              b1

              printf "%sn" "$array[@]"
              2
              1
              abcd
              Hello World
              bbb

              printf "%sn" "$!array[@]" "$array[@]" | pr -2t
              a2 2
              a1 1
              f50 abcd
              zz Hello World
              b1 bbb


              3 forks



              or this:



              paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]")
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              No fork



              to be compared to



              for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              Execution times comparission



              As last syntax don't use fork, they could be quicker:



              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              5 11 76
              real 0m0.005s
              user 0m0.000s
              sys 0m0.000s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              5 6 41
              real 0m0.008s
              user 0m0.000s
              sys 0m0.000s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              5 6 41
              real 0m0.002s
              user 0m0.000s
              sys 0m0.001s


              But this affirmation doesn't stay true if the array becomes big; if reducing forks is efficient for small process, using dedicated tools is more efficient for bigger process.



              for i in a..za..za..z;do array[$i]=$RANDOM;done


              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              17581 35163 292941
              real 0m0.150s
              user 0m0.124s
              sys 0m0.036s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              17581 17582 169875
              real 0m0.140s
              user 0m0.000s
              sys 0m0.004s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              17581 17582 169875
              real 0m0.312s
              user 0m0.268s
              sys 0m0.076s


              Remark



              As both (forked) solutions use alignment, none of them will work if any variable contains a newline. In this case, the only way is a for loop.






              share|improve this answer






















              • While looking clever, both ways are less efficient than a for. Which is a shame, really.
                – Satō Katsura
                May 22 '17 at 16:50










              • @SatoKatsura I agree, but if slower, syntax using pr is shorter... I'm not sure about pr syntax stay slower, even with big arrays!
                – F. Hauri
                May 22 '17 at 17:14







              • 2




                @MiniMax Because it doesn't produce the correct result (same elements, wrong order). You'd need to zip arrays $!array[@] and $array[@] first for that to work.
                – Satō Katsura
                May 22 '17 at 19:30







              • 1




                That last snippet with paste is longer than the for loop in the question written on one line for i in "$!array[@]"; do echo "$i=$array[$i]" ; done, but requires two subshells and an external program. How is that neater? The solution with pr also breaks if there are many elements, as it tries to paginate the output. You'd need to use something like | pr -2t -l"$#array[@]" which is starting to get hard to remember compared to the simple loop, and again, is longer than it.
                – ilkkachu
                May 22 '17 at 21:45






              • 1




                In bash, cmd1 | cmd2 means 2 forks, even if cmd1 or cmd2 or both are builtin.
                – Stéphane Chazelas
                May 23 '17 at 6:28












              up vote
              9
              down vote










              up vote
              9
              down vote









              declare -p array
              declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'


              2 fork



              Maybe this:



              printf "%sn" "$!array[@]"
              a2
              a1
              f50
              zz
              b1

              printf "%sn" "$array[@]"
              2
              1
              abcd
              Hello World
              bbb

              printf "%sn" "$!array[@]" "$array[@]" | pr -2t
              a2 2
              a1 1
              f50 abcd
              zz Hello World
              b1 bbb


              3 forks



              or this:



              paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]")
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              No fork



              to be compared to



              for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              Execution times comparission



              As last syntax don't use fork, they could be quicker:



              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              5 11 76
              real 0m0.005s
              user 0m0.000s
              sys 0m0.000s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              5 6 41
              real 0m0.008s
              user 0m0.000s
              sys 0m0.000s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              5 6 41
              real 0m0.002s
              user 0m0.000s
              sys 0m0.001s


              But this affirmation doesn't stay true if the array becomes big; if reducing forks is efficient for small process, using dedicated tools is more efficient for bigger process.



              for i in a..za..za..z;do array[$i]=$RANDOM;done


              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              17581 35163 292941
              real 0m0.150s
              user 0m0.124s
              sys 0m0.036s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              17581 17582 169875
              real 0m0.140s
              user 0m0.000s
              sys 0m0.004s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              17581 17582 169875
              real 0m0.312s
              user 0m0.268s
              sys 0m0.076s


              Remark



              As both (forked) solutions use alignment, none of them will work if any variable contains a newline. In this case, the only way is a for loop.






              share|improve this answer














              declare -p array
              declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'


              2 fork



              Maybe this:



              printf "%sn" "$!array[@]"
              a2
              a1
              f50
              zz
              b1

              printf "%sn" "$array[@]"
              2
              1
              abcd
              Hello World
              bbb

              printf "%sn" "$!array[@]" "$array[@]" | pr -2t
              a2 2
              a1 1
              f50 abcd
              zz Hello World
              b1 bbb


              3 forks



              or this:



              paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]")
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              No fork



              to be compared to



              for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done
              a2=2
              a1=1
              f50=abcd
              zz=Hello World
              b1=bbb


              Execution times comparission



              As last syntax don't use fork, they could be quicker:



              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              5 11 76
              real 0m0.005s
              user 0m0.000s
              sys 0m0.000s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              5 6 41
              real 0m0.008s
              user 0m0.000s
              sys 0m0.000s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              5 6 41
              real 0m0.002s
              user 0m0.000s
              sys 0m0.001s


              But this affirmation doesn't stay true if the array becomes big; if reducing forks is efficient for small process, using dedicated tools is more efficient for bigger process.



              for i in a..za..za..z;do array[$i]=$RANDOM;done


              time printf "%sn" "$!array[@]" "$array[@]" | pr -2t | wc
              17581 35163 292941
              real 0m0.150s
              user 0m0.124s
              sys 0m0.036s

              time paste -d= <(printf "%sn" "$!array[@]") <(printf "%sn" "$array[@]") | wc
              17581 17582 169875
              real 0m0.140s
              user 0m0.000s
              sys 0m0.004s

              time for i in "$!array[@]";do printf "%s=%sn" "$i" "$array[$i]";done | wc
              17581 17582 169875
              real 0m0.312s
              user 0m0.268s
              sys 0m0.076s


              Remark



              As both (forked) solutions use alignment, none of them will work if any variable contains a newline. In this case, the only way is a for loop.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Jan 2 at 1:44









              Jeff Schaller

              33.8k851113




              33.8k851113










              answered May 22 '17 at 16:30









              F. Hauri

              2,5691226




              2,5691226











              • While looking clever, both ways are less efficient than a for. Which is a shame, really.
                – Satō Katsura
                May 22 '17 at 16:50










              • @SatoKatsura I agree, but if slower, syntax using pr is shorter... I'm not sure about pr syntax stay slower, even with big arrays!
                – F. Hauri
                May 22 '17 at 17:14







              • 2




                @MiniMax Because it doesn't produce the correct result (same elements, wrong order). You'd need to zip arrays $!array[@] and $array[@] first for that to work.
                – Satō Katsura
                May 22 '17 at 19:30







              • 1




                That last snippet with paste is longer than the for loop in the question written on one line for i in "$!array[@]"; do echo "$i=$array[$i]" ; done, but requires two subshells and an external program. How is that neater? The solution with pr also breaks if there are many elements, as it tries to paginate the output. You'd need to use something like | pr -2t -l"$#array[@]" which is starting to get hard to remember compared to the simple loop, and again, is longer than it.
                – ilkkachu
                May 22 '17 at 21:45






              • 1




                In bash, cmd1 | cmd2 means 2 forks, even if cmd1 or cmd2 or both are builtin.
                – Stéphane Chazelas
                May 23 '17 at 6:28
















              • While looking clever, both ways are less efficient than a for. Which is a shame, really.
                – Satō Katsura
                May 22 '17 at 16:50










              • @SatoKatsura I agree, but if slower, syntax using pr is shorter... I'm not sure about pr syntax stay slower, even with big arrays!
                – F. Hauri
                May 22 '17 at 17:14







              • 2




                @MiniMax Because it doesn't produce the correct result (same elements, wrong order). You'd need to zip arrays $!array[@] and $array[@] first for that to work.
                – Satō Katsura
                May 22 '17 at 19:30







              • 1




                That last snippet with paste is longer than the for loop in the question written on one line for i in "$!array[@]"; do echo "$i=$array[$i]" ; done, but requires two subshells and an external program. How is that neater? The solution with pr also breaks if there are many elements, as it tries to paginate the output. You'd need to use something like | pr -2t -l"$#array[@]" which is starting to get hard to remember compared to the simple loop, and again, is longer than it.
                – ilkkachu
                May 22 '17 at 21:45






              • 1




                In bash, cmd1 | cmd2 means 2 forks, even if cmd1 or cmd2 or both are builtin.
                – Stéphane Chazelas
                May 23 '17 at 6:28















              While looking clever, both ways are less efficient than a for. Which is a shame, really.
              – Satō Katsura
              May 22 '17 at 16:50




              While looking clever, both ways are less efficient than a for. Which is a shame, really.
              – Satō Katsura
              May 22 '17 at 16:50












              @SatoKatsura I agree, but if slower, syntax using pr is shorter... I'm not sure about pr syntax stay slower, even with big arrays!
              – F. Hauri
              May 22 '17 at 17:14





              @SatoKatsura I agree, but if slower, syntax using pr is shorter... I'm not sure about pr syntax stay slower, even with big arrays!
              – F. Hauri
              May 22 '17 at 17:14





              2




              2




              @MiniMax Because it doesn't produce the correct result (same elements, wrong order). You'd need to zip arrays $!array[@] and $array[@] first for that to work.
              – Satō Katsura
              May 22 '17 at 19:30





              @MiniMax Because it doesn't produce the correct result (same elements, wrong order). You'd need to zip arrays $!array[@] and $array[@] first for that to work.
              – Satō Katsura
              May 22 '17 at 19:30





              1




              1




              That last snippet with paste is longer than the for loop in the question written on one line for i in "$!array[@]"; do echo "$i=$array[$i]" ; done, but requires two subshells and an external program. How is that neater? The solution with pr also breaks if there are many elements, as it tries to paginate the output. You'd need to use something like | pr -2t -l"$#array[@]" which is starting to get hard to remember compared to the simple loop, and again, is longer than it.
              – ilkkachu
              May 22 '17 at 21:45




              That last snippet with paste is longer than the for loop in the question written on one line for i in "$!array[@]"; do echo "$i=$array[$i]" ; done, but requires two subshells and an external program. How is that neater? The solution with pr also breaks if there are many elements, as it tries to paginate the output. You'd need to use something like | pr -2t -l"$#array[@]" which is starting to get hard to remember compared to the simple loop, and again, is longer than it.
              – ilkkachu
              May 22 '17 at 21:45




              1




              1




              In bash, cmd1 | cmd2 means 2 forks, even if cmd1 or cmd2 or both are builtin.
              – Stéphane Chazelas
              May 23 '17 at 6:28




              In bash, cmd1 | cmd2 means 2 forks, even if cmd1 or cmd2 or both are builtin.
              – Stéphane Chazelas
              May 23 '17 at 6:28










              up vote
              2
              down vote













              If you're looking for a shell with better associative array support, try zsh.



              In zsh (where associative arrays were added in 1998, compared to 1993 for ksh93 and 2009 for bash), $var or $(v)var expands to the (non-empty) values of the hash, $(k)var to the (non-empty) keys (in the same order), and $(kv)var to both keys and values.



              To preserve the empty values, like for arrays, you need to quote and use the @ flag.



              So to print the keys and values, it's just a matter of



              printf '%s => %sn' "$(@kv)var"


              Though to account for a possibly empty hash, you should do:



              (($#var)) && printf '%s => %sn' "$(@kv)var"


              Also note that zsh uses a much more sensible and useful array definition syntax than ksh93's (copied by bash):



              typeset -A var
              var=(k1 v1 k2 v2 '' empty '*' star)


              Which makes it a lot easier to copy or merge associative arrays:



              var2=("$(@kv)var1")
              var3+=("$(@kv)var2")
              var4=("$@kv)var4" "$(@kv)var5")


              (you can't easily copy a hash without a loop with bash, and note that bash currently doesn't support empty keys or key/values with NUL bytes).



              See also zsh array zipping features which you'll typically need to work with associative arrays:



              keys=($(<keys.txt)) values=($(<values.txt))
              hash=($keys:^values)





              share|improve this answer
























                up vote
                2
                down vote













                If you're looking for a shell with better associative array support, try zsh.



                In zsh (where associative arrays were added in 1998, compared to 1993 for ksh93 and 2009 for bash), $var or $(v)var expands to the (non-empty) values of the hash, $(k)var to the (non-empty) keys (in the same order), and $(kv)var to both keys and values.



                To preserve the empty values, like for arrays, you need to quote and use the @ flag.



                So to print the keys and values, it's just a matter of



                printf '%s => %sn' "$(@kv)var"


                Though to account for a possibly empty hash, you should do:



                (($#var)) && printf '%s => %sn' "$(@kv)var"


                Also note that zsh uses a much more sensible and useful array definition syntax than ksh93's (copied by bash):



                typeset -A var
                var=(k1 v1 k2 v2 '' empty '*' star)


                Which makes it a lot easier to copy or merge associative arrays:



                var2=("$(@kv)var1")
                var3+=("$(@kv)var2")
                var4=("$@kv)var4" "$(@kv)var5")


                (you can't easily copy a hash without a loop with bash, and note that bash currently doesn't support empty keys or key/values with NUL bytes).



                See also zsh array zipping features which you'll typically need to work with associative arrays:



                keys=($(<keys.txt)) values=($(<values.txt))
                hash=($keys:^values)





                share|improve this answer






















                  up vote
                  2
                  down vote










                  up vote
                  2
                  down vote









                  If you're looking for a shell with better associative array support, try zsh.



                  In zsh (where associative arrays were added in 1998, compared to 1993 for ksh93 and 2009 for bash), $var or $(v)var expands to the (non-empty) values of the hash, $(k)var to the (non-empty) keys (in the same order), and $(kv)var to both keys and values.



                  To preserve the empty values, like for arrays, you need to quote and use the @ flag.



                  So to print the keys and values, it's just a matter of



                  printf '%s => %sn' "$(@kv)var"


                  Though to account for a possibly empty hash, you should do:



                  (($#var)) && printf '%s => %sn' "$(@kv)var"


                  Also note that zsh uses a much more sensible and useful array definition syntax than ksh93's (copied by bash):



                  typeset -A var
                  var=(k1 v1 k2 v2 '' empty '*' star)


                  Which makes it a lot easier to copy or merge associative arrays:



                  var2=("$(@kv)var1")
                  var3+=("$(@kv)var2")
                  var4=("$@kv)var4" "$(@kv)var5")


                  (you can't easily copy a hash without a loop with bash, and note that bash currently doesn't support empty keys or key/values with NUL bytes).



                  See also zsh array zipping features which you'll typically need to work with associative arrays:



                  keys=($(<keys.txt)) values=($(<values.txt))
                  hash=($keys:^values)





                  share|improve this answer












                  If you're looking for a shell with better associative array support, try zsh.



                  In zsh (where associative arrays were added in 1998, compared to 1993 for ksh93 and 2009 for bash), $var or $(v)var expands to the (non-empty) values of the hash, $(k)var to the (non-empty) keys (in the same order), and $(kv)var to both keys and values.



                  To preserve the empty values, like for arrays, you need to quote and use the @ flag.



                  So to print the keys and values, it's just a matter of



                  printf '%s => %sn' "$(@kv)var"


                  Though to account for a possibly empty hash, you should do:



                  (($#var)) && printf '%s => %sn' "$(@kv)var"


                  Also note that zsh uses a much more sensible and useful array definition syntax than ksh93's (copied by bash):



                  typeset -A var
                  var=(k1 v1 k2 v2 '' empty '*' star)


                  Which makes it a lot easier to copy or merge associative arrays:



                  var2=("$(@kv)var1")
                  var3+=("$(@kv)var2")
                  var4=("$@kv)var4" "$(@kv)var5")


                  (you can't easily copy a hash without a loop with bash, and note that bash currently doesn't support empty keys or key/values with NUL bytes).



                  See also zsh array zipping features which you'll typically need to work with associative arrays:



                  keys=($(<keys.txt)) values=($(<values.txt))
                  hash=($keys:^values)






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered May 23 '17 at 6:51









                  Stéphane Chazelas

                  288k54534871




                  288k54534871




















                      up vote
                      1
                      down vote













                      Since typeset does what you want why not just edit its output?



                      typeset -p array | sed s/^.*(// | tr -d ")'"" | tr "[" "n" | sed s/]=/' = '/


                      gives



                      a2 = 2 
                      a1 = 1
                      b1 = bbb


                      Where



                      array='([a2]="2" [a1]="1" [b1]="bbb" )'


                      Verbose but it's pretty easy to see how the formatting works: just execute the pipeline with progressively more of the sed and tr commands. Modify them to suit pretty printing tastes.






                      share|improve this answer






















                      • That kind of a pipeline is bound to fail the moment some of the keys or values of the array contain any of the characters you're replacing, like parenthesis, brackets or quotes. And a pipeline of seds and tr's isn't even much simpler than a for loop with printf.
                        – ilkkachu
                        May 22 '17 at 21:19










                      • Also, you do know that tr translate character-by-character, it doesn't match strings? tr "]=" " =" changes "]" to a space and an = to an =, regardless of position. So you could probably just combine all three tr's to one.
                        – ilkkachu
                        May 22 '17 at 21:22











                      • Very true about some of the non-alphanumeric characters gumming this up. However anything that has to deal them gets an order of magnitude more complex and less readable so unless there's a really good reason to have them in your data feed and that's stated in the question I assume they're filtered out before we got here. Should always have your explicit caveat tho. I find these pipelines to be simpler, for example and debugging purposes, than a printf glob that either works perfectly or blows up in your face. Here you make one simple change per element, test it, then add 1 more.
                        – Nadreck
                        May 23 '17 at 1:10










                      • My bad! Got my _tr_s and _sed_s totally mixed up! Fixed in the latest edit.
                        – Nadreck
                        May 23 '17 at 1:12














                      up vote
                      1
                      down vote













                      Since typeset does what you want why not just edit its output?



                      typeset -p array | sed s/^.*(// | tr -d ")'"" | tr "[" "n" | sed s/]=/' = '/


                      gives



                      a2 = 2 
                      a1 = 1
                      b1 = bbb


                      Where



                      array='([a2]="2" [a1]="1" [b1]="bbb" )'


                      Verbose but it's pretty easy to see how the formatting works: just execute the pipeline with progressively more of the sed and tr commands. Modify them to suit pretty printing tastes.






                      share|improve this answer






















                      • That kind of a pipeline is bound to fail the moment some of the keys or values of the array contain any of the characters you're replacing, like parenthesis, brackets or quotes. And a pipeline of seds and tr's isn't even much simpler than a for loop with printf.
                        – ilkkachu
                        May 22 '17 at 21:19










                      • Also, you do know that tr translate character-by-character, it doesn't match strings? tr "]=" " =" changes "]" to a space and an = to an =, regardless of position. So you could probably just combine all three tr's to one.
                        – ilkkachu
                        May 22 '17 at 21:22











                      • Very true about some of the non-alphanumeric characters gumming this up. However anything that has to deal them gets an order of magnitude more complex and less readable so unless there's a really good reason to have them in your data feed and that's stated in the question I assume they're filtered out before we got here. Should always have your explicit caveat tho. I find these pipelines to be simpler, for example and debugging purposes, than a printf glob that either works perfectly or blows up in your face. Here you make one simple change per element, test it, then add 1 more.
                        – Nadreck
                        May 23 '17 at 1:10










                      • My bad! Got my _tr_s and _sed_s totally mixed up! Fixed in the latest edit.
                        – Nadreck
                        May 23 '17 at 1:12












                      up vote
                      1
                      down vote










                      up vote
                      1
                      down vote









                      Since typeset does what you want why not just edit its output?



                      typeset -p array | sed s/^.*(// | tr -d ")'"" | tr "[" "n" | sed s/]=/' = '/


                      gives



                      a2 = 2 
                      a1 = 1
                      b1 = bbb


                      Where



                      array='([a2]="2" [a1]="1" [b1]="bbb" )'


                      Verbose but it's pretty easy to see how the formatting works: just execute the pipeline with progressively more of the sed and tr commands. Modify them to suit pretty printing tastes.






                      share|improve this answer














                      Since typeset does what you want why not just edit its output?



                      typeset -p array | sed s/^.*(// | tr -d ")'"" | tr "[" "n" | sed s/]=/' = '/


                      gives



                      a2 = 2 
                      a1 = 1
                      b1 = bbb


                      Where



                      array='([a2]="2" [a1]="1" [b1]="bbb" )'


                      Verbose but it's pretty easy to see how the formatting works: just execute the pipeline with progressively more of the sed and tr commands. Modify them to suit pretty printing tastes.







                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited May 23 '17 at 0:30

























                      answered May 22 '17 at 19:36









                      Nadreck

                      1679




                      1679











                      • That kind of a pipeline is bound to fail the moment some of the keys or values of the array contain any of the characters you're replacing, like parenthesis, brackets or quotes. And a pipeline of seds and tr's isn't even much simpler than a for loop with printf.
                        – ilkkachu
                        May 22 '17 at 21:19










                      • Also, you do know that tr translate character-by-character, it doesn't match strings? tr "]=" " =" changes "]" to a space and an = to an =, regardless of position. So you could probably just combine all three tr's to one.
                        – ilkkachu
                        May 22 '17 at 21:22











                      • Very true about some of the non-alphanumeric characters gumming this up. However anything that has to deal them gets an order of magnitude more complex and less readable so unless there's a really good reason to have them in your data feed and that's stated in the question I assume they're filtered out before we got here. Should always have your explicit caveat tho. I find these pipelines to be simpler, for example and debugging purposes, than a printf glob that either works perfectly or blows up in your face. Here you make one simple change per element, test it, then add 1 more.
                        – Nadreck
                        May 23 '17 at 1:10










                      • My bad! Got my _tr_s and _sed_s totally mixed up! Fixed in the latest edit.
                        – Nadreck
                        May 23 '17 at 1:12
















                      • That kind of a pipeline is bound to fail the moment some of the keys or values of the array contain any of the characters you're replacing, like parenthesis, brackets or quotes. And a pipeline of seds and tr's isn't even much simpler than a for loop with printf.
                        – ilkkachu
                        May 22 '17 at 21:19










                      • Also, you do know that tr translate character-by-character, it doesn't match strings? tr "]=" " =" changes "]" to a space and an = to an =, regardless of position. So you could probably just combine all three tr's to one.
                        – ilkkachu
                        May 22 '17 at 21:22











                      • Very true about some of the non-alphanumeric characters gumming this up. However anything that has to deal them gets an order of magnitude more complex and less readable so unless there's a really good reason to have them in your data feed and that's stated in the question I assume they're filtered out before we got here. Should always have your explicit caveat tho. I find these pipelines to be simpler, for example and debugging purposes, than a printf glob that either works perfectly or blows up in your face. Here you make one simple change per element, test it, then add 1 more.
                        – Nadreck
                        May 23 '17 at 1:10










                      • My bad! Got my _tr_s and _sed_s totally mixed up! Fixed in the latest edit.
                        – Nadreck
                        May 23 '17 at 1:12















                      That kind of a pipeline is bound to fail the moment some of the keys or values of the array contain any of the characters you're replacing, like parenthesis, brackets or quotes. And a pipeline of seds and tr's isn't even much simpler than a for loop with printf.
                      – ilkkachu
                      May 22 '17 at 21:19




                      That kind of a pipeline is bound to fail the moment some of the keys or values of the array contain any of the characters you're replacing, like parenthesis, brackets or quotes. And a pipeline of seds and tr's isn't even much simpler than a for loop with printf.
                      – ilkkachu
                      May 22 '17 at 21:19












                      Also, you do know that tr translate character-by-character, it doesn't match strings? tr "]=" " =" changes "]" to a space and an = to an =, regardless of position. So you could probably just combine all three tr's to one.
                      – ilkkachu
                      May 22 '17 at 21:22





                      Also, you do know that tr translate character-by-character, it doesn't match strings? tr "]=" " =" changes "]" to a space and an = to an =, regardless of position. So you could probably just combine all three tr's to one.
                      – ilkkachu
                      May 22 '17 at 21:22













                      Very true about some of the non-alphanumeric characters gumming this up. However anything that has to deal them gets an order of magnitude more complex and less readable so unless there's a really good reason to have them in your data feed and that's stated in the question I assume they're filtered out before we got here. Should always have your explicit caveat tho. I find these pipelines to be simpler, for example and debugging purposes, than a printf glob that either works perfectly or blows up in your face. Here you make one simple change per element, test it, then add 1 more.
                      – Nadreck
                      May 23 '17 at 1:10




                      Very true about some of the non-alphanumeric characters gumming this up. However anything that has to deal them gets an order of magnitude more complex and less readable so unless there's a really good reason to have them in your data feed and that's stated in the question I assume they're filtered out before we got here. Should always have your explicit caveat tho. I find these pipelines to be simpler, for example and debugging purposes, than a printf glob that either works perfectly or blows up in your face. Here you make one simple change per element, test it, then add 1 more.
                      – Nadreck
                      May 23 '17 at 1:10












                      My bad! Got my _tr_s and _sed_s totally mixed up! Fixed in the latest edit.
                      – Nadreck
                      May 23 '17 at 1:12




                      My bad! Got my _tr_s and _sed_s totally mixed up! Fixed in the latest edit.
                      – Nadreck
                      May 23 '17 at 1:12










                      up vote
                      0
                      down vote













                      One more option is to list all the variables and grep for the one you want.



                      set | grep -e '^aa='



                      I use this for debugging. I doubt that it is very performant since it lists all the variables.



                      If you were doing this often you could make it a function like this:



                      aap() grep -e "^$1=";



                      Unfortunately when we check performance using time:




                      $ time aap aa
                      aa=([0]="abc")
                      .
                      real 0m0.014s
                      user 0m0.003s
                      sys 0m0.006s



                      Therefore, if you were doing this very often, you'd want @F.Hauri's NO FORKS version because it is so much faster.






                      share|improve this answer
























                        up vote
                        0
                        down vote













                        One more option is to list all the variables and grep for the one you want.



                        set | grep -e '^aa='



                        I use this for debugging. I doubt that it is very performant since it lists all the variables.



                        If you were doing this often you could make it a function like this:



                        aap() grep -e "^$1=";



                        Unfortunately when we check performance using time:




                        $ time aap aa
                        aa=([0]="abc")
                        .
                        real 0m0.014s
                        user 0m0.003s
                        sys 0m0.006s



                        Therefore, if you were doing this very often, you'd want @F.Hauri's NO FORKS version because it is so much faster.






                        share|improve this answer






















                          up vote
                          0
                          down vote










                          up vote
                          0
                          down vote









                          One more option is to list all the variables and grep for the one you want.



                          set | grep -e '^aa='



                          I use this for debugging. I doubt that it is very performant since it lists all the variables.



                          If you were doing this often you could make it a function like this:



                          aap() grep -e "^$1=";



                          Unfortunately when we check performance using time:




                          $ time aap aa
                          aa=([0]="abc")
                          .
                          real 0m0.014s
                          user 0m0.003s
                          sys 0m0.006s



                          Therefore, if you were doing this very often, you'd want @F.Hauri's NO FORKS version because it is so much faster.






                          share|improve this answer












                          One more option is to list all the variables and grep for the one you want.



                          set | grep -e '^aa='



                          I use this for debugging. I doubt that it is very performant since it lists all the variables.



                          If you were doing this often you could make it a function like this:



                          aap() grep -e "^$1=";



                          Unfortunately when we check performance using time:




                          $ time aap aa
                          aa=([0]="abc")
                          .
                          real 0m0.014s
                          user 0m0.003s
                          sys 0m0.006s



                          Therefore, if you were doing this very often, you'd want @F.Hauri's NO FORKS version because it is so much faster.







                          share|improve this answer












                          share|improve this answer



                          share|improve this answer










                          answered 33 mins ago









                          xer0x

                          1113




                          1113



























                               

                              draft saved


                              draft discarded















































                               


                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function ()
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f366581%2fbash-associative-array-printing%23new-answer', 'question_page');

                              );

                              Post as a guest













































































                              Popular posts from this blog

                              Peggy Mitchell

                              Palaiologos

                              The Forum (Inglewood, California)