57

I'm writing a bash script to modify a config file which contains a bunch of key/value pairs. How can I read the key and find the value and possibly modify it?

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Progress Programmer
  • 7,076
  • 14
  • 49
  • 54

8 Answers8

118

A wild stab in the dark for modifying a single value:

sed -c -i "s/\($TARGET_KEY *= *\).*/\1$REPLACEMENT_VALUE/" $CONFIG_FILE

assuming that the target key and replacement value don't contain any special regex characters, and that your key-value separator is "=". Note, the -c option is system dependent and you may need to omit it for sed to execute.

For other tips on how to do similar replacements (e.g., when the REPLACEMENT_VALUE has '/' characters in it), there are some great examples here.

Community
  • 1
  • 1
Cascabel
  • 479,068
  • 72
  • 370
  • 318
  • 17
    -c seems to be invalid in my environment. When I get rid of -c, it works. Thanks. – Progress Programmer Mar 17 '10 at 21:48
  • The `-c` option tells sed to copy the file when it shuffles it (instead of renaming) - it prevents changing ownership and messing with read-only files. One of those habits I've picked up to save myself from stupid mistakes (especially with `find ... | xargs sed -i`). – Cascabel Mar 17 '10 at 22:04
  • 1
    Did I really get two downvotes for this answer? Not only did I manage to guess what the OP wanted, it's short and efficient. Anyone care to explain? – Cascabel Mar 18 '10 at 00:10
  • Any chance you can explain how to do this if i need to do this same thing, except i need to do it through ssh into a remote machine. If you know an alternative way to do the same thing, that would suffice as well. Thanks – prolink007 May 11 '11 at 15:55
  • 1
    @prolink: `ssh [opts] [user@]hostname command` will log in then run the given command. If the command is complex enough, you might want to store it in a script on the remote so you can just run `ssh path/to/script`. – Cascabel May 11 '11 at 18:08
  • 6
    If anyone perform it on a Mac environment, you might want to remove -c and add '' after -i. just like `sed -i '' "s...." $CONFIG_FILE` – Yang Oct 08 '15 at 01:59
  • 9
    Very useful script. If all your keys start at the beginning of the lines, consider adding `^` before `$TARGET_KEY`, otherwise if you have two keys like `even=2` and `noteven=1` you'll end up changing both. – acm Mar 31 '17 at 10:06
  • 1
    How about adding the value in case it doesn't exist previously? – Michael Chourdakis May 01 '18 at 10:37
  • Does not work with tabs, does not take slashes in $REPLACEMENT_VALUE – Sergey Kandaurov Nov 06 '19 at 07:45
23

Hope this helps someone. I created a self contained script, which required config processing of sorts.

#!/bin/bash
CONFIG="/tmp/test.cfg"

# Use this to set the new config value, needs 2 parameters. 
# You could check that $1 and $2 is set, but I am lazy
function set_config(){
    sudo sed -i "s/^\($1\s*=\s*\).*\$/\1$2/" $CONFIG
}

# INITIALIZE CONFIG IF IT'S MISSING
if [ ! -e "${CONFIG}" ] ; then
    # Set default variable value
    sudo touch $CONFIG
    echo "myname=\"Test\"" | sudo tee --append $CONFIG
fi

# LOAD THE CONFIG FILE
source $CONFIG

echo "${myname}" # SHOULD OUTPUT DEFAULT (test) ON FIRST RUN
myname="Erl"
echo "${myname}" # SHOULD OUTPUT Erl
set_config myname $myname # SETS THE NEW VALUE
Rudi Strydom
  • 4,417
  • 5
  • 21
  • 30
  • I doubt if this works with TOML, or even YAML or JSON, as they have sections. It works fine for just key-value pairs though – Debargha Roy Dec 16 '20 at 17:58
5

Assuming that you have a file of key=value pairs, potentially with spaces around the =, you can delete, modify in-place or append key-value pairs at will using awk even if the keys or values contain special regex sequences:

# Using awk to delete, modify or append keys
# In case of an error the original configuration file is left intact
# Also leaves a timestamped backup copy (omit the cp -p if none is required)
CONFIG_FILE=file.conf
cp -p "$CONFIG_FILE" "$CONFIG_FILE.orig.`date \"+%Y%m%d_%H%M%S\"`" &&
awk -F '[ \t]*=[ \t]*' '$1=="keytodelete" { next } $1=="keytomodify" { print "keytomodify=newvalue" ; next } { print } END { print "keytoappend=value" }' "$CONFIG_FILE" >"$CONFIG_FILE~" &&
mv "$CONFIG_FILE~" "$CONFIG_FILE" ||
echo "an error has occurred (permissions? disk space?)"
vladr
  • 65,483
  • 18
  • 129
  • 130
4
sed "/^$old/s/\(.[^=]*\)\([ \t]*=[ \t]*\)\(.[^=]*\)/\1\2$replace/" configfile
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
3

So I can not take any credit for this as it is a combination of stackoverflow answers and help from irc.freenode.net #bash channel but here are bash functions now to both set and read config file values:

# https://stackoverflow.com/a/2464883
# Usage: config_set filename key value
function config_set() {
  local file=$1
  local key=$2
  local val=${@:3}

  ensureConfigFileExists "${file}"

  # create key if not exists
  if ! grep -q "^${key}=" ${file}; then
    # insert a newline just in case the file does not end with one
    printf "\n${key}=" >> ${file}
  fi

  chc "$file" "$key" "$val"
}

function ensureConfigFileExists() {
  if [ ! -e "$1" ] ; then
    if [ -e "$1.example" ]; then
      cp "$1.example" "$1";
    else
      touch "$1"
    fi
  fi
}

# thanks to ixz in #bash on irc.freenode.net
function chc() { gawk -v OFS== -v FS== -e 'BEGIN { ARGC = 1 } $1 == ARGV[2] { print ARGV[4] ? ARGV[4] : $1, ARGV[3]; next } 1' "$@" <"$1" >"$1.1"; mv "$1"{.1,}; }

# https://unix.stackexchange.com/a/331965/312709
# Usage: local myvar="$(config_get myvar)"
function config_get() {
    val="$(config_read_file ${CONFIG_FILE} "${1}")";
    if [ "${val}" = "__UNDEFINED__" ]; then
        val="$(config_read_file ${CONFIG_FILE}.example "${1}")";
    fi
    printf -- "%s" "${val}";
}
function config_read_file() {
    (grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}

at first I was using the accepted answer's sed solution: https://stackoverflow.com/a/2464883/2683059

however if the value has a / char it breaks

tempcke
  • 700
  • 8
  • 15
0

in general it's easy to extract the info with grep and cut:


cat "$FILE" | grep "^${KEY}${DELIMITER}" | cut -f2- -d"$DELIMITER"

to update you could do something like this:


mv "$FILE" "$FILE.bak"
cat "$FILE.bak" | grep -v "^${KEY}${DELIMITER}" > "$FILE"
echo "${KEY}${DELIMITER}${NEWVALUE}" >> "$FILE"

this would not maintain the order of the key-value pairs obviously. add error checking to make sure you don't lose your data.

  • if you run out of disk space or if for whatever reason you cannot create `FILE` then your solution will leave `FILE` missing (one would have to manually rename `FILE.bak` back to `FILE` to recover.) – vladr Mar 17 '10 at 21:08
0

I have done this:

new_port=$1
sed "s/^port=.*/port=$new_port/" "$CONFIG_FILE" > /yourPath/temp.x
mv /yourPath/temp.x "$CONFIG_FILE"

This will change port= to port=8888 in your config file if you choose 8888 as $1 for example.

Timothée
  • 19
  • 1
  • 7
-1

Suppose your config file is in below format:

CONFIG_NUM=4
CONFIG_NUM2=5
CONFIG_DEBUG=n

In your bash script, you can use:

CONFIG_FILE=your_config_file
. $CONFIG_FILE

if [ $CONFIG_DEBUG == "y" ]; then
    ......
else
    ......
fi

$CONFIG_NUM, $CONFIG_NUM2, $CONFIG_DEBUG is what you need.

After your read the values, write it back will be easy:

echo "CONFIG_DEBUG=y" >> $CONFIG_FILE
pihentagy
  • 5,975
  • 9
  • 39
  • 58
datasunny
  • 281
  • 1
  • 4
  • 14
  • 10
    That won't replace the value, just add it again at the bottom, it's completely dependent on the config file being a valid bash script, and... wow, what if there's something malicious in the config file? – Cascabel Mar 17 '10 at 18:29