2

This question already has an answer here:

I write a lot of adhoc scripts for adding lines to sudoers, sysctl.conf etc. Usually I use the lineinfile ansible module. Sometimes, however, I don't get to do that so I use :

WHAT_I_WANT='my line of text = something'
WHAT_TO_REPLACE='my line of text = .*'
FILE_TO_EDIT=conf_file.conf
if ( ! grep -q "^$WHAT_I_WANT\$" FILE_TO_EDIT ); then
    echo  "$WHAT_I_WANT" >> FILE_TO_EDIT
else
    sed -i "s/$WHAT_TO_REPLACE/$WHAT_I_WANT/g' FILE_TO_EDIT
fi

My guess is there's a more efficient way to do this. Ideally with one line and one language (and also in a way that allows regexp matching, removing commented out lines and backing up files) But I don't know what that is.

marked as duplicate by Thomas Dickey, Rui F Ribeiro, Scott, Community Aug 11 '17 at 21:19

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 2
    Well, typing a longer text three times seems inefficient and error prone to me. Use a var. – LotPings Aug 11 '17 at 15:24
  • 1
    @lot can't argue with that, but not exactly the point to the question! – Peter Turner Aug 11 '17 at 15:30
  • You just maybe want to add /g global flag to the sed command if you want to change every occurrence of that text. Sorry for posting but I don't have yet enough of the points to comment. – in1t3r Aug 11 '17 at 15:32
  • Yeah, I can (and probably would) do that. I don't just want to change every occurrence of the text, I also want to add it to the file if it doesn't already exist. Also, it's better that you answer in an answer than in the comments, even if it is a short answer. – Peter Turner Aug 11 '17 at 15:36
  • @PeterTurner true, but this isn't really an answer s a comment would indeed be more appropriate. – terdon Aug 11 '17 at 15:39
1
  • Using extra long variable names to replace long strings is IMO counterproductive.
  • To have variables be expanded in grep/sed commands use double quotes.
  • Using wildcards .* in variable asignments will carry out filename expansion(globbing)
  • The following script uses conditional execution on success && or fail || instead of an if.

REPL='something'
PATT='my line of text = '
FILE=conf_file.conf

cat $FILE
echo '========='

grep -q "^$PATT" $FILE && (sed -i "s/^$PATT.*$/$PATT$REPL/" $FILE) || (echo "$PATT$REPL" >> $FILE)

cat $FILE
  • 1
    OP needs to match some regex which means using regex quantifiers like * - using those chars in variable assignments does not carry out filename expansion and as long as you quote your vars that should not be a problem. Run var=* and then compare the output of echo $var and echo "$var". – don_crissti Aug 11 '17 at 18:19
  • @don_crissti How do you expand the vars conataining REs in sed/grep commands and at the same time escape from globbing? – LotPings Aug 11 '17 at 18:32
  • As I said, you quote your vars... I don't see what's so unclear... – don_crissti Aug 11 '17 at 18:41
  • It's not my problem, I just pointed out what was wrong with OPs approach. – LotPings Aug 11 '17 at 18:46
1

Not actually a one-liner, but you could do this with one invocation of sed:

sed -e '
   /pat/{s//repl/g;h;}
   $!b
   G
   s/\n..*//;t
   s/$/repl/
' yourfile

N.B.: I've deliberateley used pat and repl for pattern and replacement respectively, and not the shell variables as you've done. Basically for 2 main reasons, they would get in the way of the sed code flow + for correctness we need to quote both, and differently at that, to make them work. That job I leave it to you.

Flow: Say, the file never had a /pat/ then all lines are taken to stdout by the $!b command and the last line when appended by G just sees an empty hold so the t is not taken and we have an append operation.

When we see a /pat/ line, then it is s///-ed and hold area is marked. if it's not the last line, we simply break out to stdout. For eof, we do a check of hold and since it's non-empty(assuming repl is NONEMPTY, the test path would be taken after removing the hold from the pattern space.


Perl affords the clarity to match the intent word-for-word in the code:

perl -lne '$a += s/pat/repl/g,print}{print q[repl] unless $a'

To be read as: The variable $a serves as the counter for the number of substitutions. At the end, we would append to file when no subs were made earlier.

And as for supplying the pattern/replacement info via variables, we can do it:

WHAT_I_WANT='my line of text = something'
WHAT_TO_REPLACE='my line of text = .*'
FILE_TO_EDIT=conf_file.conf

perl -li -sn -e '
        $a += s/$pat/$repl/g,print}{print $repl unless $a
' -- -pat="$WHAT_I_WANT" -repl="$WHAT_TO_REPLACE" -- "$FILE_TO_EDIT"

Not the answer you're looking for? Browse other questions tagged or ask your own question.