Return value of sed for no match
LinuxJsonSedLinux Problem Overview
I'm using sed
for updating my JSON configuration file in the runtime.
Sometimes, when the pattern doesn't match in the JSON file, sed
still exits with return code 0.
Returning 0 means successful completion, but why does sed
return 0 if it doesn't find the proper pattern and update the file? Is there a workaround for that?
Linux Solutions
Solution 1 - Linux
as @cnicutar commented, the return code of a command means if the command was executed successfully. has nothing to do with the logic you implemented in the codes/scripts.
so if you have:
echo "foo"|sed '/bar/ s/a/b/'
sed will return 0
but if you write some syntax/expression errors, or the input/file doesn't exist, sed cannot execute your request, sed will return 1.
workaround
this is actually not workaround. sed has q
command: (from man page):
q [exit-code]
here you can define exit-code as you want. For example '/foo/!{q100}; {s/f/b/}'
will exit with code 100 if foo
isn't present, and otherwise perform the substitution f->b and exit with code 0.
Matched case:
kent$ echo "foo" | sed '/foo/!{q100}; {s/f/b/}'
boo
kent$ echo $?
0
Unmatched case:
kent$ echo "trash" | sed '/foo/!{q100}; {s/f/b/}'
trash
kent$ echo $?
100
I hope this answers your question.
edit
I must add that, the above example is just for one-line processing. I don't know your exact requirement. when you want to get exit 1
. one-line unmatched or the whole file. If whole file unmatching case, you may consider awk, or even do a grep
before your text processing...
Solution 2 - Linux
This might work for you (GNU sed):
sed '/search-string/{s//replacement-string/;h};${x;/./{x;q0};x;q1}' file
If the search-string
is found it will be replaced with replacement-string
and at end-of-file sed
will exit with 0
return code. If no substitution takes place the return code will be 1
.
A more detailed explanation:
In sed the user has two registers at his disposal: the pattern space (PS) in which the current line is loaded into (minus the linefeed) and a spare register called the hold space (HS) which is initially empty.
The general idea is to use the HS as a flag to indicate if a substitution has taken place. If the HS is still empty at the end of the file, then no changes have been made, otherwise changes have occurred.
The command /search-string/
matches search-string
with whatever is in the PS and if it is found to contain the search-string
the commands between the following curly braces are executed.
Firstly the substitution s//replacement-string/
(sed uses the last regexp i.e. the search-string
, if the lefthand-side is empty, so s//replacement-string
is the same as s/search-string/replacement-string/
) and following this the h
command makes a copy of the PS and puts it in the HS.
The sed command $
is used to recognise the last line of a file and the following then occurs.
First the x
command swaps the two registers, so the HS becomes the PS and the PS becomes the HS.
Then the PS is searched for any character /./
(.
means match any character) remember the HS (now the PS) was initially empty until a substitution took place. If the condition is true the x
is again executed followed by q0
command which ends all sed processing and sets the return code to 0
. Otherwise the x
command is executed and the return code is set to 1
.
N.B. although the q
quits sed processing it does not prevent the PS from being reassembled by sed and printed as per normal.
Another alternative:
sed '/search-string/!ba;s//replacement-string/;h;:a;$!b;p;x;/./Q;Q1' file
or:
sed '/search-string/,${s//replacement-string/;b};$q1' file
Solution 3 - Linux
These answers are all too complicated. What is wrong with writing a bit of shell script that uses grep to figure out if the thing you want to replace is there then using sed to replace it?
grep -q $TARGET_STRING $file
if [ $? -eq 0 ]
then
echo "$file contains the old site"
sed -e "s|${TARGET_STRING}|${NEW_STRING}|g" ....
fi
Solution 4 - Linux
For 1 line of input. To avoid repeating the /pattern/:
When s
succeeds to substitute, use t
to jump conditionally to a label, e.g. x
. Otherwise use q
to quit with an exit code, e.g. 100
:
's/pattern/replacement/;tx;q100;:x'
Example:
$ echo 1 > one
$ < one sed 's/1/replaced-it/;tx;q1;:x'
replaced-it
$ echo $?
0
$ < one sed 's/999/replaced-it/;tx;q100;:x'
1
$ echo $?
100
https://www.gnu.org/software/sed/manual/html_node/Branching-and-flow-control.html
Solution 5 - Linux
We have the answer above but it took some time for me work out what is happening. I am trying to provide a simple explanation for basic user of sed like me.
Lets consider the example:
echo "foo" | sed '/foo/!{q100}; {s/f/b/}'
Here we have two sed commands. First one is '/foo/!{q100}'
This command actually check the pattern matching and return exist code 100
if no match. Consider following examples, -n
is used to silent the output so we only get exist code.
This example foo matches so exit code return is 0
echo "foo" | sed -n '/foo/!{q100}'; echo $?
0
This example input is foo
and we try match boo
so no match and exit code 100
is returned
echo "foo" | sed -n '/boo/!{q100}'; echo $?
100
So if my requirement is only to check a pattern match or not I can use
echo "<input string>" | sed -n '/<pattern to match>/!{q<exit-code>}'
More examples:
echo "20200206" | sed -n '/[0-9]*/!{q100}' && echo "Matched" || echo "No Match"
Matched
echo "20200206" | sed -n '/[0-9]{2}/!{q100}' && echo "Matched" || echo "No Match"
No Match
Second command is '{s/f/b/}'
is to replace the f
in foo
with b
which I used many times.
Solution 6 - Linux
Below is the pattern we use with sed -rn
or sed -r
.
The entire search and replace command ("s/.../.../...") is optional. If the search and replace is used, for speed and having already matched $matchRe, we use as fast a $searchRe value as possible, using . where the character does not need to be re-verified and .{$len} for fixed length sections of the pattern.
The return value for none found is $notFoundExit.
/$matchRe/{s/$searchRe/$replacement/$options; Q}; q$notFoundExit
For the following reasons:
- No time wasted testing for both matched and unmatched case
- No time wasted copying to or from buffers
- No superfluous branches
- Reasonable flexibility
Varying the case of Q commands will vary the behavior depending on when the exit should occur. Behaviors involving the application of Boolean logic to a multiple line input requires more complexity in the solution.
Solution 7 - Linux
For any number of input lines:
sed --quiet 's/hello/HELLO/;t1;b2;:1;h;:2;p;${g;s/..*//;tok;q1;:ok}'
Fills hold space on match, and checks it after the last line.
Returns status 1
if no match in file.
s/hello/HELLO
- substitution to check fort1
- jump to label1
if substitution succeededb2
- jump to label2
unconditionally:1
- label1
h
- copy pattern to hold space (when substitution succeeded):2
- label2
p
- print pattern space, unconditionally${ ... }
- match last line, evaluate block insideg
- copy hold space into pattern space (non-empty if first substitution succeded before)s/..*//
- dummy substitution, to set branch-flagtok
- jump to labelok
(if dummy substitution succeeded on non-empty hold space)q1
- exit with error status 1:ok
- labelok
Solution 8 - Linux
As we already know, when sed fails to match then it simply returns its input string - no error has occurred. It is true that a difference between the input and output strings implies a match, but a match does not imply a difference in the strings; after all sed could have simply matched all of the input characters. The flaw is created in the following example
h=$(echo "$g" | sed 's/.*\(abc[[:digit:]]\).*/\1/g')
if [ ! "$h" = "$g" ]; then
echo "1"
else
echo "2"
fi
where g=Xabc1
gives 1, while setting g=abc1
gives 2; yet both of these input strings are matched by sed! So, it can be hard to determine whether sed has matched or not. A solution:
h=$(echo "fix${g}ed" | sed 's/.*\(abc[[:digit:]]\).*/\1/g')
if [ ! "$h" = "fix${g}ed" ]; then
echo "1"
else
echo "2"
fi
in which case the 1 is printed if-and-only-if sed has matched.
Solution 9 - Linux
I had wanted to truncate a file by quitting when the match was found (and exclude the matching line). This is handy when a process that adds lines at the end of the file may be re-run. "Q;Q1" didn't work but simply "Q1" did, as follows:
if sed -i '/text I wanted to find/Q1' file.txt then insert blank line at end of file + new lines fi insert just the new lines without the blank line