[ACCEPTED]-grep a pattern and output non-matching part of line-grep

Accepted answer
Score: 13

You could use sed:

$ sed -n "/$PAT/s/$PAT//p" $file

The only problem is that 7 it'll return an exit code of 0 as long as 6 the pattern is good, even if the pattern 5 can't be found.


Explanation

The -n parameter tells sed not 4 to print out any lines. Sed's default is 3 to print out all lines of the file. Let's 2 look at each part of the sed program in between 1 the slashes. Assume the program is /1/2/3/4/5:

  1. /$PAT/: This says to look for all lines that matches pattern $PAT to run your substitution command. Otherwise, sed would operate on all lines, even if there is no substitution.
  2. /s/: This says you will be doing a substitution
  3. /$PAT/: This is the pattern you will be substituting. It's $PAT. So, you're searching for lines that contain $PAT and then you're going to substitute the pattern for something.
  4. //: This is what you're substituting for $PAT. It is null. Therefore, you're deleting $PAT from the line.
  5. /p: This final p says to print out the line.

Thus:

  • You tell sed not to print out the lines of the file as it processes them.
  • You're searching for all lines that contain $PAT.
  • On these lines, you're using the s command (substitution) to remove the pattern.
  • You're printing out the line once the pattern is removed from the line.
Score: 10

How about using a combination of grep, sed and 6 $PIPESTATUS to get the correct exit-status?

$ echo Humans are not proud of their ancestors, and rarely invite
  them round to dinner | grep dinner | sed -n "/dinner/s/dinner//p"
Humans are not proud of their ancestors, and rarely invite them round to 

$ echo $PIPESTATUS[1]
0[1]

The members 5 of the $PIPESTATUS array hold the exit status of each 4 respective command executed in a pipe. $PIPESTATUS[0] holds 3 the exit status of the first command in 2 the pipe, $PIPESTATUS[1] the exit status of the second 1 command, and so on.

Score: 2

Your $tags will never have a value because 7 you send it to /dev/null. Besides from that 6 little problem, there is no input to grep.

echo hello |grep "^he" -q ; 
ret=$? ; 
if [ $ret -eq 0 ]; 
then 
echo there is he in hello; 
fi

a 5 successful return code is 0.

...here is 4 1 take at your 'problem':

pat="most of "; 
data="The apples are ripe. I will use most of them for jam.";  
echo $data |grep "$pat" -q; 
ret=$?; 
[ $ret -eq 0 ] && echo $data |sed "s/$pat//"
The apples are ripe. I will use them for jam.

... exact same 3 thing?:

echo The apples are ripe. I will use most of them for jam. | sed ' s/most\ of\ //'

It seems to me you have confused 2 the basic concepts. What are you trying 1 to do anyway?

Score: 0

I am going to answer the title of the question 59 directly instead of considering the detail 58 of the question itself:

"grep a pattern 57 and output non-matching part of line"

The 56 title to this question is important to me 55 because the pattern I am searching for contains 54 characters that sed will assign special 53 meaning to. I want to use grep because I 52 can use -F or --fixed-strings to cause grep 51 to interpret the pattern literally. Unfortunately, sed 50 has no literal option, but both grep and 49 bash have the ability to interpret patterns 48 without considering any special characters.

Note: In 47 my opinion, trying to backslash or escape 46 special characters in a pattern appears 45 complex in code and is unreliable because 44 it is difficult to test. Using tools which 43 are designed to search for literal text 42 leaves me with a comfortable 'that will 41 work' feeling without considering POSIX.

I 40 used both grep and bash to produce the result 39 because bash is slow and my use of fast 38 grep creates a small output from a large 37 input. This code searches for the literal 36 twice, once during grep to quickly extract 35 matching lines and once during =~ to remove 34 the match itself from each line.

    while IFS= read -r || [[ -n "$RESULT" ]]; do
        if [[ "$REPLY" =~ (.*)("$LITERAL_PATTERN")(.*) ]]; then
            printf '%s\n' "${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
        else
            printf "NOT-REFOUND" # should never happen
            exit 1
        fi
    done < <(grep -F "$LITERAL_PATTERN" < "$INPUT_FILE")

Explanation:

IFS= Reassigning 33 the input field separator is a special prefix 32 for a read statement. Assigning IFS to the 31 empty string causes read to accept each line 30 with all spaces and tabs literally until 29 end of line (assuming IFS is default space-tab-newline).

-r Tells 28 read to accept backslashes in the input stream 27 literally instead of considering them as 26 the start of an escape sequence.

$REPLY Is created 25 by read to store characters from the input stream. The 24 newline at the end of each line will NOT 23 be in $REPLY.

|| [[ -n "$REPLY" ]] The logical or causes the 22 while loop to accept input which is not 21 newline terminated. This does not need to 20 exist because grep always provides a trailing 19 newline for every match. But, I habitually 18 use this in my read loops because without it, characters 17 between the last newline and the end of 16 file will be ignored because that causes 15 read to fail even though content is successfully 14 read.

=~ (.*)("$LITERAL_PATTERN")(.*) ]] Is a standard bash regex test, but 13 anything in quotes in taken as a literal. If 12 I wanted =~ to consider the regex characters 11 in contained in $PATTERN, then I would need 10 to eliminate the double quotes.

"${BASH_REMATCH[@]}" Is created 9 by [[ =~ ]] where [0] is the entire match 8 and [N] is the contents of the match in 7 the Nth set of parentheses.

Note: I do not 6 like to reassign stdin to a while loop because 5 it is easy to error and difficult to see 4 what is happening later. I usually create 3 a function for this type of operation which 2 acts typically and expects file_name parameters 1 or reassignment of stdin during the call.

More Related questions