Please consider this snippet:

X=$(grep -m1 'some-pattern' some-file | sed -n 's/.* //p')

I want to put last word in a variable if some pattern condition is matched for lines in arbitrary text file

My problem is that variable X has CR or LF or CRLF at the end, depending on source file, which I want to get rid of, as it interferes with later operation I intend to do.
I even tried something like:

X=$(grep -m1 'some-pattern' some-file | sed -n 's/.* \([A-Za-z]\+\)/\1/p')

thus expecting sed output to be limited on [A-Za-z]+ but there are still this nuisance bytes inside X variable.

How can I get rid of it, without using too much code like see what bytes are at the end with xxd then cut it and similar complications?


It seems like awk would be a better choice for your needs, as these problems don't exist due to the fact that it can use fields and records:

x=$(awk '/some-pattern/ { sub(/\r$/, "") ; printf("%s", $NF) ; exit }' some-file)

The substitution avoids your issue with CRLF line endings.

sub(/\r$/, "") removes the trailing CR, if it exists. As awk treats \n as the record (line) separator, you do not need to strip it, as it isn't in the data being looked at.

printf("%s", $NF) prints the final field ($NF) with no trailing newline (print and some other awk functions append a newline by default).

exit happens after the first two actions -- this is the equivalent of m1 in your grep command line. This ensures that awk exits after the execution of the previous two commands -- and since these commands are issued upon a match, and awk evaluates data in a FIFO manner, this will only print the first match.

  • Thanks, it looks elegant but unfortunately CRLF is still inside X – zetah Mar 3 '12 at 1:23
  • :) Now it doesn't look elegant any more and it is still no good – zetah Mar 3 '12 at 1:26
  • @zetah - There won't be a CR, but there will be an LF. I had a hard time understanding what you want from the question, hopefully my edit does what you want. – Chris Down Mar 3 '12 at 1:27
  • OK, this time it does it good - output last word in a line if that line satisfies some pattern condition - don't know, maybe it's clear to me because I have this problem, and then hard to explain as non-native english speaker. Anyhow, I'll wait a bit more if someone addresses this with grep/sed solution instead awk (which I don't understand), and if not I'll use it. Thanks – zetah Mar 3 '12 at 1:40
  • @zetah - I'll add an explanation so you can understand it better, one second. – Chris Down Mar 3 '12 at 1:41

The `` or $() will remove the newline from the end, but to do this programatically, use tr.

grep -m1 'some-pattern' some-file | sed -n 's/.* //p' | tr -d '\012\015'

This will remove the carriage return and/or the newline from the string.

What might be the problem is how you then output the result. For example, by default, echo adds a newline. You may want to use echo -n or printf.

  • This will also remove carriage returns that may occur throughout the string, which might not be desired. – Chris Down Mar 3 '12 at 2:07
  • Yes, while it is possible to have a carriage return embedded within a single line, it is extremely rare. The -m1 will ensure that there is only one line output, which in all likelihood, would have the carriage return at the end. – Arcege Mar 3 '12 at 2:23
  • ah tr... interesting, works both on LF and CRLF files. I would think \010\013 for some reason, and also \f\r works correctly. About the result: I don't actually put the output in variable but as variable enclosed in $() in pattern for grep match - some pipe | grep -o " $(...) ". Thanks for comments – zetah Mar 3 '12 at 2:37

I prefer this way

grep -m1 'some-pattern' some-file | sed -n 's/.* //p' | tr -d '\n'

This works for me:

grep -m1 'some-pattern' some-file | sed -n 's/.* //p' | tr -d "\n" | tr -d "\r"

Why not simply let sed do the [\r\f] cleanup:

# using Bash's $'string' idiom (that decodes ANSI C escape sequences)
# cf. http://wiki.bash-hackers.org/syntax/quoting#ansi_c_like_strings
- X="$(grep -m1 'some-pattern' some-file | sed -n 's/.* //p')"
+ X="$(grep -m1 'some-pattern' some-file | sed -n -e $'s/[\r\f]*$//' -e 's/.* //p')"

Your second approach lacks a final regex to catch the trailing CR, \r.

# sample code to remove trailing \r with sed
# cf. http://en.wikipedia.org/wiki/Regular_expression#POSIX_character_classes
printf 'a b c\r' | sed -n 's/^.* \([[:alpha:]]\{1,\}\)/\1/p' | od -c
printf 'a b c\r' | sed -n 's/^.* \([[:alpha:]]\{1,\}\)[[:space:]]*/\1/p' | od -c

# keeps trailing space after c
printf 'a b c \r' | sed -n 's/^.* \([[:alpha:] ]\{1,\}\)[[:space:]]*/\1/p' | od -b

The normal version of grep (including grep -P) always outputs a line feed with its match, so if you only have one result (or you only want the final added line feed to be removed), it suffices to simply remove the final character of the output, which you can do by piping it through head -c-1.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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