Search code examples
bashshellawkdeclare

Bash declare not liking values with spaces


This is a continuation of awk gsub not replacing all instances of period in field.

I have some input:

$ cat ./ipv4settings.txt
ipv4.gateway:                           192.168.2.1
ipv4.routes:                            --
ipv4.route-metric:                      -1
ipv4.route-table:                       0 (unspec)

I'm able to generate the desired output (somewhat):

$ awk 'BEGIN{FS=":[[:space:]]+";OFS="="}{gsub("[.]|-","_",$1);$1=$1;print $1"="$2 }' ./ipv4settings.txt
ipv4_gateway=192.168.2.1
ipv4_routes=--
ipv4_route_metric=-1
ipv4_route_table=0 (unspec)

What I want to do next is declare variables for each line of output. I have used several variations using declare:

$awk 'BEGIN{FS=":[[:space:]]+";OFS="="}{gsub("[.]|-","_",$1);$1=$1;print $1"="$2 }' ./ipv4settings.txt
ipv4_gateway=192.168.2.1
ipv4_routes=--
ipv4_route_metric=-1
ipv4_route_table=0 (unspec)
$ declare $( awk 'BEGIN{FS=":[[:space:]]+";OFS="="}{gsub("[.]|-","_",$1);$1=$1;print $1"="$2 }' ./ipv4settings.txt )
-bash: declare: `(unspec)': not a valid identifier

I tried quoting the entire line of output from awk (declare not a valid identifier bash)(https://www.baeldung.com/linux/awk-print-quote-characters):

$ awk 'BEGIN{FS=":[[:space:]]+";OFS="="}{gsub("[.]|-","_",$1);$1=$1;print "\042"$1"="$2"\042" }' ./ipv4settings.txt
"ipv4_gateway=192.168.2.1"
"ipv4_routes=--"
"ipv4_route_metric=-1"
"ipv4_route_table=0 (unspec)"
$ declare $( awk 'BEGIN{FS=":[[:space:]]+";OFS="="}{gsub("[.]|-","_",$1);$1=$1;print "\042"$1"="$2"\042" }' ./ipv4settings.txt )
-bash: declare: `"ipv4_gateway=192.168.2.1"': not a valid identifier
-bash: declare: `"ipv4_routes=--"': not a valid identifier
-bash: declare: `"ipv4_route_metric=-1"': not a valid identifier
-bash: declare: `"ipv4_route_table=0': not a valid identifier
-bash: declare: `(unspec)"': not a valid identifier

... or just the value portion:

$ awk 'BEGIN{FS=":[[:space:]]+";OFS="="}{gsub("[.]|-","_",$1);$1=$1;print $1"=\042"$2"\042" }' ./ipv4settings.txt
ipv4_gateway="192.168.2.1"
ipv4_routes="--"
ipv4_route_metric="-1"
ipv4_route_table="0 (unspec)"
$ declare $( awk 'BEGIN{FS=":[[:space:]]+";OFS="="}{gsub("[.]|-","_",$1);$1=$1;print $1"=\042"$2"\042" }' ./ipv4settings.txt )
-bash: declare: `(unspec)"': not a valid identifier

How do I get declare to work with a variable value with a space?


Solution

  • Modifying my answer to OP's previous question:

    $ cat ipv4.awk
    BEGIN { sq = "\x27" }
    
    #######
    # if 1st field will convert to an invalid variable name then print
    # a message to stderr (printing to stdout will cause problems with
    # the follow-on source call)
    
    ! ($1 ~ /^[[:alnum:]._-]+:/) {
    
            print "ERROR: Invalid entry:",$0 > "/dev/stderr"
            next
    }
    
    /^ipv4[.](gateway|route)/    {
    
            pos   = index($0,":")
            var   = substr($0,1,pos-1)
            value = substr($0,pos+1)
    
            gsub(/[.-]/,"_",var)
            gsub(/^[[:space:]]*|[[:space:]]*$/,"",value)
            gsub(sq,sq "\"" sq "\"" sq,value)             # see NOTES below
    
            print var "=" sq value sq
    }
    

    NOTES:

    • pertaining to the gsub(sq,sq "\"" sq "\"" sq,value) call ...
    • per the back-n-forth comments with Charles Duffy this additional gsub() call has been added to address potential injection attacks
    • Charles Duffy's proposed gsub(/[']/,"'\"'\"'",value) could also be used but must be placed in a file (eg, ipv4.awk) and invoked via awk -f ipv4.awk; calling gsub(/[']/,"'\"'\"'",value) from an inline awk script (eg, OP's example awk scripts) will generate syntax errors (due to the special nature of the single quote for inline awk scripts)

    Adding some additional (bogus) lines on the end (borrowing a few from Charles Duffy's answer):

    $ cat ipv4settings.txt
    ipv4.gateway:                           192.168.2.1
    ipv4.routes:                            --
    ipv4.route-metric:                      -1
    ipv4.route-table:                       0 (unspec)
    ipv6.routes:                            should ignore this line
    ipv4.route-wifi.network-ssid-name:      hi$(touch evil)'$(touch evil)'\
    ipv4.route-evil.variable.$(touch evil): harmless
    ipv4.route-example.with.colons:         hi:world: yup: still data
    ipv4.route-stuff:0   (unspec)  *   "'leave:these:colons:alone   
    

    NOTE: the last line has a few spaces on the end

    Generating the variable=value pairs:

    $ awk -f ipv4.awk ipv4settings.txt
    ipv4_gateway='192.168.2.1'
    ipv4_routes='--'
    ipv4_route_metric='-1'
    ipv4_route_table='0 (unspec)'
    ipv4_route_wifi_network_ssid_name='hi$(touch evil)'"'"'$(touch evil)'"'"'\'
    ERROR: Invalid entry: ipv4.route-evil.variable.$(touch evil): harmless
    ipv4_route_example_with_colons='hi:world: yup: still data'
    ipv4_route_stuff='0   (unspec)  *   "'"'"'leave:these:colons:alone'
    

    One idea using source to load the variables into the current environment:

    $ source <(awk -f ipv4.awk ipv4settings.txt)
    ERROR: Invalid entry: ipv4.route-evil.variable.$(touch evil): harmless
    
    $ typeset -p ipv4_gateway ipv4_routes ipv4_route_metric ipv4_route_table ipv4_route_wifi_network_ssid_name ipv4_route_example_with_colons ipv4_route_stuff
    declare -- ipv4_gateway="192.168.2.1"
    declare -- ipv4_routes="--"
    declare -- ipv4_route_metric="-1"
    declare -- ipv4_route_table="0 (unspec)"
    declare -- ipv4_route_wifi_network_ssid_name="hi\$(touch evil)'\$(touch evil)'\\"
    declare -- ipv4_route_example_with_colons="hi:world: yup: still data"
    declare -- ipv4_route_stuff="0   (unspec)  *   \"'leave:these:colons:alone"