I cant figure out the proper FPAT to get each comma separated value. Using bash on Debian 12.5… I have a file PING.cfg with contents:
NODE01="router,10.0.0.1,5"
NODE02="dns1,68.6.16.30,35"
NODE03="dns2,1.1.1.1,35"
Those get used as variables sourced in multiple scripts, I want a script to assign each of the comma separated values to a variable in a for loop, maybe something like this:
for n in `grep NODE PING.cfg`
do
NODENAME=$(echo "$n" | awk -v FPAT='"[^"]+"' -F',' '{ print $1 }')
HOSTIP=$(echo "$n" | awk -v FPAT='"[^"]+"' -F',' '{ print $2 }')
echo "NODE is $NODENAME and HOSTIP is $HOSTIP"
done
But awk does not seem to be reading inside the quotes with my FPAT pattern, the output for $1 includes the entire variable:
NODE is NODE01="router and HOSTIP is 10.0.0.1
Tried FPAT to specify quotes.
10
Assuming you need these values inside a shell loop for some reason, this might be what you want, using any awk:
$ cat tst.sh
#!/usr/bin/env bash
while read -r nodename hostip rest; do
printf 'nodename is %s and hostip is %sn' "$nodename" "$hostip"
done < <(awk -F'[",]' '/^NODE/{print $2, $3}' PING.cfg)
$ ./tst.sh
nodename is router and hostip is 10.0.0.1
nodename is dns1 and hostip is 68.6.16.30
nodename is dns2 and hostip is 1.1.1.1
I’m using lower case variable names for the reasons described at Correct Bash and shell script variable capitalization.
1
As bash is tagged, you can also use regular expressions with capture groups, which can be accessed via the ${BASH_REMATCH[@]}
array. A well-composed regex can even be used to only consider lines containing NODE
, replacing your initial grep
filter:
while read -r line; do
[[ "$line" =~ ^(NODE[0-9]+)="([0-9a-z]+),([0-9.]+),([0-9]+)"$ ]] &&
# BASH_REMATCH ╰────[1]───╯ ╰───[2]───╯ ╰──[3]──╯ ╰──[4]─╯
echo "NODE is ${BASH_REMATCH[2]} and HOSTIP is ${BASH_REMATCH[3]}"
done < PING.cfg
NODE is router and HOSTIP is 10.0.0.1
NODE is dns1 and HOSTIP is 68.6.16.30
NODE is dns2 and HOSTIP is 1.1.1.1
As you already drive this via a script just use read to parse each line:
while IFS=, read NODE HOSTIP _
do
echo "NODE is ${NODE/*"/} and HOSTIP is $HOSTIP"
done < PING.cfg
and example run:
NODE is router and HOSTIP is 10.0.0.1
NODE is dns1 and HOSTIP is 68.6.16.30
NODE is dns2 and HOSTIP is 1.1.1.1
I don’t think you’re using a version of awk
that supports FPAT
. But even if it did, it wouldn’t do what you seem to expect.
Instead, start by using quotes as the field separators. Then the string dns1,68.6.16.30,35
will be field 2. You can then use split()
to split this at comma delimiters.
NODENAME=$(echo "$n" | awk -F'"' '{ split($2, a, /,/); print a[1] }')
HOSTIP=$(echo "$n" | awk -F'"' '{ split($2, a, /,/); print a[2] }')
This solution doesn’t require any GNU extensions, these are all POSIX features.
3
Those get used as variables sourced in multiple scripts
If the file is already sourced (or sourcing them won’t be an issue), you can access the variables starting with NODE
using the Bash parameter expansion ${!prefix@}
:
. PING.cfg
for n in "${!NODE@}"; do readarray -td, vals <<< "${!n}"
echo "NODE is ${vals[0]} and HOSTIP is ${vals[1]}"
done
# or
for n in "${!NODE@}"; do IFS=, read -r node hostip value <<< "${!n}"
echo "NODE is $node and HOSTIP is $hostip"
done
# or
for n in "${!NODE@}"; do printf "NODE is %s and HOSTIP is %sn"
"$(cut -d, -f1 <<< "${!n}")" "$(cut -d, -f2 <<< "${!n}")"
done
NODE is router and HOSTIP is 10.0.0.1
NODE is dns1 and HOSTIP is 68.6.16.30
NODE is dns2 and HOSTIP is 1.1.1.1
You file has "
-sheared values and 2nd field has ,
-sheared values, therefore I suggest using cut
twice. Let PING.cfg
content be
NODE01="router,10.0.0.1,5"
NODE02="dns1,68.6.16.30,35"
NODE03="dns2,1.1.1.1,35"
then
for n in `grep NODE PING.cfg`
do
NODENAME=$(echo "$n" | cut -d '"' -f 2 | cut -d ',' -f 1)
HOSTIP=$(echo "$n" | cut -d '"' -f 2 | cut -d ',' -f 2)
echo "NODE is $NODENAME and HOSTIP is $HOSTIP"
done
gives following output
NODE is router and HOSTIP is 10.0.0.1
NODE is dns1 and HOSTIP is 68.6.16.30
NODE is dns2 and HOSTIP is 1.1.1.1
Observe that it will give same results as using GNU AWK
with [",]
field separator (FS
) and retrieving 2nd and 3rd field respectively if and only if there is never "
before =
and there is never ,
before =
, therefore if you can provide that always hold true I suggest using more verbose cut-cut approach.
something like …, my two bits ..
awk -V
GNU Awk 5.0.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.2.0)
Copyright (C) 1989, 1991-2019 Free Software Foundation.
# using poster's supplied input ...
awk -F'"' '{split($2,bits,","); print "NODE is " bits[1] " and HOSTIP is " bits[2]}' < PING.cfg
NODE is router and HOSTIP is 10.0.0.1
NODE is dns1 and HOSTIP is 68.6.16.30
NODE is dns2 and HOSTIP is 1.1.1.1