aenagy
aenagy

Reputation: 121

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?

Upvotes: 3

Views: 182

Answers (3)

markp-fuso
markp-fuso

Reputation: 35256

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"

Upvotes: 5

Charles Duffy
Charles Duffy

Reputation: 295698

In native bash, avoiding the security issues implicit with source or eval:

#!/usr/bin/env bash
regex='^([[:alnum:]._-]+)[:][[:space:]]+([^[:space:]].*)$'
while IFS= read -r line; do
  [[ $line =~ $regex ]] || {
    echo "WARNING: Unrecognized line: $line" >&2
    continue
  }
  varname=${BASH_REMATCH[1],,}
  varname=${varname//[-.]/_}
  printf -v "$varname" %s "${BASH_REMATCH[2]}"
  [[ $debug ]] && declare -p "$varname" >&2
done <ipv4settings.txt

If run with debug=1, given your stated input, this outputs:

declare -- ipv4_gateway="192.168.2.1"
declare -- ipv4_routes="--"
declare -- ipv4_route_metric="-1"
declare -- ipv4_route_table="0 (unspec)"

Malicious inputs are either ignored or treated as data without being parsed as code:

wifi.network-ssid-name:      hi$(touch evil)'$(touch evil)'\
evil.variable.$(touch evil): harmless
example.with.colons:         hi:world: yup: still data

...emits:

declare -- wifi_network_ssid_name="hi\$(touch evil)'\$(touch evil)'\\"
WARNING: Unrecognized line: evil.variable.$(touch evil): harmless
declare -- example_with_colons="hi:world: yup: still data"

Notice how the $(...) sequences were assigned as data, not executed as code; no file named evil was created.

Upvotes: 4

ticktalk
ticktalk

Reputation: 922

would something along that shown below be suitable? (NB:using gnu awk !GNU Awk 5.0.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.2.0))

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

$ cat fixme.awk
{printf("%s=\"%s\"\n",gensub(/[\.-]/,"_","g",$1),gensub(/ {2,}/,"","g",$2))}

$ awk -F: -f fixme.awk ipv4settings.txt
ipv4_gateway="192.168.2.1"
ipv4_routes="--"
ipv4_route_metric="-1"
ipv4_route_table="0 (unspec)"

Upvotes: 0

Related Questions