Skip to content

Instantly share code, notes, and snippets.

@kokumura
Created May 25, 2018 08:39
Show Gist options
  • Save kokumura/a6d819ddcb4efe54c5541fc15e1d0347 to your computer and use it in GitHub Desktop.
Save kokumura/a6d819ddcb4efe54c5541fc15e1d0347 to your computer and use it in GitHub Desktop.
lineinfile in Shell Script
# Ansible 'lineinfile' like function in Shell Script.
# Works on both Bash and Zsh.
function lineinfile(){
if [[ $# != 3 ]];then
local THIS_FUNC_NAME="${funcstack[1]-}${FUNCNAME[0]-}"
echo "$THIS_FUNC_NAME - 3 arguments are expected. given $#. args=[$@]" >&2
echo "usage: $THIS_FUNC_NAME PATTERN LINE FILE" >&2
return 1
fi
local PATTERN="$1"
local LINE="$2"
local FILE="$3"
if grep -E -q "${PATTERN}" "${FILE}" ;then
## solution 1: works with GNU sed well, but not works with BSD sed.
# sed -E -i '' "/${PATTERN//\//\\/}/c${LINE}" "${FILE}"
## solution 2: works with both (GNU|BSD) sed, but get useless *.bak file generated.
# sed -E -i.bak "/${PATTERN//\//\\/}/c\\"$'\n'"${LINE}" "${FILE}"
## solution 3: give up to use sed, using perl instead.
PATTERN="${PATTERN}" LINE="${LINE}" perl -i -nle 'if(/$ENV{"PATTERN"}/){print $ENV{"LINE"}}else{print}' "${FILE}"
else
echo "$LINE" >> "$FILE"
fi
}
######################
# example
######################
# write some lines to 'test.txt'
cat <<EOF > test.txt
foo = FOO1 # first occurence
bar = BAR
foo = FOO2 # second occurence
EOF
# usage: lineinfile PATTERN LINE FILE
# if some lines in FILE matches PATTEN, all of them are replaced with LINE.
lineinfile '^foo\s*=\s*' "foo = POO # changed!" test.txt
# if no lines in FILE matches PATTERN, LINE is appended to end of FILE.
lineinfile '^baz\s*=' "baz = BAZ" test.txt
cat test.txt
# now 'test.txt' will contain:
#
# foo = POO # changed!
# bar = BAR
# foo = POO # changed!
# baz = BAZ
@tymik
Copy link

tymik commented Jul 26, 2023

If the *.bak is useless, why is it there in the first place?
from GNU sed man:

-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied).

And for BSD sed the -i '' should work as it doesn't seem to be optional there.

I don't really see a reason to keep the solution 1 and solution 2 here in place instead of combining them into one that is free from:

  • but not works with BSD sed
  • but get useless *.bak file generated

It seems like:

sed -E -i '' "/${PATTERN//\//\\/}/c\\"$'\n'"${LINE}" "${FILE}"

could do the trick (I haven't tested that).

Maybe there was a reason having both solutions that I miss and it's worth mentioning it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment