17

I have a file that I need to look up a value by key using a shell script. The file looks like:

HereIsAKey This is the value

How can I do something like:

MyVar=Get HereIsAKey

and then MyVar should equal "This is the value". The key has no whitespace and the value should be everything following whitespace after the key.

Edwin Evans
  • 2,726
  • 5
  • 34
  • 47

6 Answers6

24

if HereIsAKey is unique in your file, try this with grep:

myVar=$(grep -Po "(?<=^HereIsAKey ).*" file)
Kent
  • 189,393
  • 32
  • 233
  • 301
  • 1
    Great! One question as I'm new to scripting. I want a function like: GetConfigValue and it would take HereIsAKey as a parameter. How can I put the passed parameter into the regular expression? – Edwin Evans Mar 12 '13 at 16:49
  • This is fine: echo "(grep -Po \"(?<=^" "$1" " ).*\" file)", but I'm having trouble assigning it to a variable. I get MyParam: command not found. – Edwin Evans Mar 12 '13 at 17:34
  • Related: https://stackoverflow.com/questions/4990575/need-bash-shell-script-for-reading-name-value-pairs-from-a-file – tripleee Oct 10 '17 at 09:37
  • Hi @Kent, I used your solution but I am getting two `=` instead of one `=` sign. My file is slightly different than OP. I have file with: `MyVar1=abc@.com` `MyVar2=ab/xyz12`. There is already a single `=` in file. Can you please let me know what needs to be changed here? – Preeti Jul 26 '22 at 11:12
  • @Preeti it's hard to give a working solution without seeing your origin file. but, FYI, the `=` is not a problem for `look ahead`. you can give this a try `myVar=$(grep -Po "(?<=^MyKey=).*" file)` I suggest you understand what the regex means, then you're able to solve many problems in the same kind in the future. – Kent Jul 27 '22 at 12:06
9

If you only need one variable at a time, you can do something like this:

#!/bin/bash
cat file | while read key value; do
  echo $key
  echo $value
done

The problem with this solution: The variables are only valid inside the loop. So don't try to do $key=$value and use it after the loop.

Update: Another way is I/O redirection:

exec 3<file
while read -u3 key value; do
  eval "$key='$value'"
done
exec 3<&-
echo "$keyInFile1"
echo "$anotherKey"
Daniel Alder
  • 5,031
  • 2
  • 45
  • 55
  • The left-hand side of an assignment cannot be a parameter expansion – chepner Mar 12 '13 at 17:08
  • found a solution for the assignment – Daniel Alder Mar 13 '13 at 12:25
  • 1
    That's a really unnecessarily verbose fix. Just take your existing `while read` loop, put ` – Charles Duffy Mar 13 '17 at 14:42
  • @CharlesDuffy Today, I wonder myself about it. I copied it from somewhere and believed it was the smartest possible implementation. Debians installers contain lots of such redirections... – Daniel Alder Mar 13 '17 at 14:51
  • They typically do that because they want to be able to use the original standard input inside the loop. Of course, it's possible that some of them just did a cargo cult copy from a script where this was actually necessary. – tripleee Apr 26 '17 at 09:55
  • BTW, I would suggest replacing the `eval` with a less-dangerous indirect assignment method -- `printf -v "$key" %s "$value"`, for example. The current code will behave very, *very* badly if you have a value with literal single quotes -- say, `$(rm -rf ~)'$(rm -rf ~)'`; one and only one of those will be escaped. – Charles Duffy Aug 20 '18 at 01:05
9

If you don't have a grep that supports Perl-compatible regular expressions, the following seems to work:

VAR=$(grep "^$KEY " file | cut -d' ' -f2-)
Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380
  • Not only is this more readable and portable, it can also handle variable whitespace size if you cut on the equals: `grep "^$KEY" $FILE | cut -d'=' -f2-` – 7yl4r Oct 04 '17 at 17:17
5

If the file is unsorted, lookups will be slow:

my_var=$( awk '/^HereIsAKey/ { $1=""; print $0; exit}' value-file )

If the file is sorted, you can get a faster lookup with

my_var=$( look HereIsAkey value-file | cut -d ' ' -f 2- )
William Pursell
  • 204,365
  • 48
  • 270
  • 300
0

I use a property file that is shared across multiple languages, I use a pair of functions:

load_properties() {
    local aline= var= value=
    for file in config.properties; do
        [ -f $file ] || continue
        while read aline; do
            aline=${aline//\#*/}
            [[ -z $aline ]] && continue
            read var value <<<$aline
            [[ -z $var ]] && continue
            eval __property_$var=\"$value\"
            # You can remove the next line if you don't need them exported to subshells
            export __property_$var
        done <$file
    done
}

get_prop() {
    local var=$1 key=$2
    eval $var=\"\$__property_$key\"
}

load_properties reads from the config.properties file populating a set of variables __property_... for each line in the file, get_prop then allows the setting of a variable based on loaded properties. It works for most of the cases that are needed.

Yes, I do realize there's an eval in there, which makes it unsafe for user input, but it works for what I needed it to do.

Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
0
get () {
    while read -r key value; do
        if [ "$key" = "$1" ]; then
            echo "$value"
            return 0
        fi
    done
    return 1
}

The two return statements aren't strictly necessary, but provide nice exit codes to indicate success or failure at finding the given key. They can also help distinguish between "the key has a empty string for the value" and "the key was not found".

chepner
  • 497,756
  • 71
  • 530
  • 681