15

I'm attempting to read a config file that is formatted as follows:

USER = username
TARGET = arrows

I realize that if I got rid of the spaces, I could simply source the config file, but for security reasons I'm trying to avoid that. I know there is a way to read the config file line by line. I think the process is something like:

  1. Read lines into an array
  2. Filter out all of the lines that start with #
  3. search for the variable names in the array

After that I'm lost. Any and all help would be greatly appreciated. I've tried something like this with no success:

backup2.config>cat ~/1

grep '^[^#].*' | while read one two;do
    echo $two
done

I pulled that from a forum post I found, just not sure how to modify it to fit my needs since I'm so new to shell scripting.

http://www.linuxquestions.org/questions/programming-9/bash-shell-program-read-a-configuration-file-276852/


Would it be possible to automatically assign a variable by looping through both arrays?

for (( i = 0 ; i < ${#VALUE[@]} ; i++ ))
do
    "${NAME[i]}"=VALUE[i]           
done
echo $USER

Such that calling $USER would output "username"? The above code isn't working but I know the solution is something similar to that.

Gerry
  • 153
  • 1
  • 1
  • 5

4 Answers4

12

The following script iterates over each line in your input file (vars in my case) and does a pattern match against =. If the equal sign is found it will use Parameter Expansion to parse out the variable name from the value. It then stores each part in it's own array, name and value respectively.

#!/bin/bash

i=0
while read line; do
  if [[ "$line" =~ ^[^#]*= ]]; then
    name[i]=${line%% =*}
    value[i]=${line#*= }
    ((i++))
  fi
done < vars

echo "total array elements: ${#name[@]}"
echo "name[0]: ${name[0]}"
echo "value[0]: ${value[0]}"
echo "name[1]: ${name[1]}"
echo "value[1]: ${value[1]}"
echo "name array: ${name[@]}"
echo "value array: ${value[@]}"

Input

$ cat vars
sdf
USER = username
TARGET = arrows
asdf
as23

Output

$ ./varscript
total array elements: 2
name[0]: USER
value[0]: username
name[1]: TARGET
value[1]: arrows
name array: USER TARGET
value array: username arrows
IMSoP
  • 89,526
  • 13
  • 117
  • 169
SiegeX
  • 135,741
  • 24
  • 144
  • 154
  • 1
    Great, but would you mind explaining it so that I can better understand what it is doing? – Gerry Dec 14 '10 at 00:36
  • The only problem with this solution, is that even if a line is commented and has [CODE] #Use the example USER = whatever to define this configuration [/CODE] then it will include "whatever to define this configuration" in the VALUE array. – Gerry Dec 14 '10 at 01:29
  • @Gerry The current iteration of the script should handle the case of `#Use the example USER = whatever` but it won't work if you have `USER = whatever #this is an inline comment`. Check to make sure you have the latest version as I edited it a few times. – SiegeX Dec 14 '10 at 01:44
  • To make that work you have to remove the quotes from the regex string. Take a look at http://stackoverflow.com/questions/218156/bash-regex-with-quotes – Ortwin Angermeier May 15 '13 at 08:34
  • @ortang thanks, I've updated my answer to reflect the new way that bash 3.2 and beyond treat quoted regex strings. – SiegeX May 16 '13 at 07:39
  • 2
    @SiegeX, in order to match key=value in config file (note lack of spaces before/after '=' sign) but if we use the expressions: name[i]=${line%%=*} value[i]=${line##*=} seems to take it ok. Isn't it better this way? – danirod May 16 '13 at 11:21
  • I've taken the liberty of changing `value[i]=${line##*= }` to `value[i]=${line#*= }`; this changes the pattern from looking for the *last* `=` to looking for the *first* (`##` greedily deletes, we want to non-greedily delete), so that `foo=bar=baz` will now have a name of "foo" and a value of "bar=baz". – IMSoP Jun 06 '13 at 09:34
  • Great example. HOw does one do this with a config containing array definitions? – Felipe Alvarez Nov 22 '13 at 06:06
5

First, USER is a shell environment variable, so it might be better if you used something else. Using lowercase or mixed case variable names is a way to avoid name collisions.

#!/bin/bash
configfile="/path/to/file"
shopt -s extglob
while IFS='= ' read lhs rhs
do
    if [[ $lhs != *( )#* ]]
    then
        # you can test for variables to accept or other conditions here
        declare $lhs=$rhs
    fi
done < "$configfile"

This sets the vars in your file to the value associated with it.

echo "Username: $USER, Target: $TARGET"

would output

Username: username, Target: arrows

Another way to do this using keys and values is with an associative array:

Add this line before the while loop:

declare -A settings

Remove the declare line inside the while loop and replace it with:

    settings[$lhs]=$rhs

Then:

# set keys
user=USER
target=TARGET
# access values
echo "Username: ${settings[$user]}, Target: ${settings[$target]}"

would output

Username: username, Target: arrows

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
3

I have a script which only takes a very limited number of settings, and processes them one at a time, so I've adapted SiegeX's answer to whitelist the settings I care about and act on them as it comes to them.

I've also removed the requirement for spaces around the = in favour of ignoring any that exist using the trim function from another answer.

function trim()
{
    local var=$1;
    var="${var#"${var%%[![:space:]]*}"}";   # remove leading whitespace characters
    var="${var%"${var##*[![:space:]]}"}";   # remove trailing whitespace characters
    echo -n "$var";
}

while read line; do
    if [[ "$line" =~ ^[^#]*= ]]; then
        setting_name=$(trim "${line%%=*}");
        setting_value=$(trim "${line#*=}");

        case "$setting_name" in
            max_foos)
                prune_foos $setting_value;
            ;;
            max_bars)
                prune_bars $setting_value;
            ;;
            *)
                echo "Unrecognised setting: $setting_name";
            ;;
        esac;
    fi
done <"$config_file";
Community
  • 1
  • 1
IMSoP
  • 89,526
  • 13
  • 117
  • 169
1

Thanks SiegeX. I think the later updates you mentioned does not reflect in this URL.

I had to edit the regex to remove the quotes to get it working. With quotes, array returned is empty.

i=0
while read line; do
  if [[ "$line" =~ ^[^#]*= ]]; then
    name[i]=${line%% =*}
    value[i]=${line##*= }
    ((i++))
  fi
 done < vars

A still better version is .

i=0
while read line; do
if [[ "$line" =~ ^[^#]*= ]]; then
        name[i]=`echo $line | cut -d'=' -f 1`
            value[i]=`echo $line | cut -d'=' -f 2`
        ((i++))
fi
done < vars

The first version is seen to have issues if there is no space before and after "=" in the config file. Also if the value is missing, i see that the name and value are populated as same. The second version does not have any of these. In addition it trims out unwanted leading and trailing spaces.

This version reads values that can have = within it. Earlier version splits at first occurance of =.

i=0
while read line; do
if [[ "$line" =~ ^[^#]*= ]]; then
        name[i]=`echo $line | cut -d'=' -f 1`
            value[i]=`echo $line | cut -d'=' -f 2-`
        ((i++))
fi
done < vars
Sibi JV
  • 253
  • 2
  • 3
  • 8