2

I have a file with following contents

..\..\src\modules\core\abc\abc.cpp
..\..\src\modules\core\something\xyz\xyz.cpp
..\..\src\other_modules\new_core\something\pqr\pqr.cpp
..\..\src\other_modules\new_core\something\pqr\abc.cpp

The result I am expecting is

..\..\src\abc\abc.cpp
..\..\src\xyz\xyz.cpp
..\..\src\pqr\pqr.cpp
..\..\src\pqr\abc.cpp

How can I achieve this using sed?

I am unable to write an regular expression to capture two groups at the same time.

  1. initial group (....\src) - this will be same in all the lines
  2. variable group (abc\abc.cpp) or (xyz\xyz.cpp) or (pqr\pqr.cpp) or (pqr\abc.cpp)

4 Answers 4

2

With BSD sed or recent versions of GNU sed (for older versions, replace -E with -r):

sed -E 's#(.*\\src).*(\\[^\]+\\[^\]+$)#\1\2#' file.txt
  • # is used as the delimiter for substitution (s) command of sed, to avoid ambiguity involving \s in the input

  • (.*\\src) matches upto src from start, and put the match in captured group 1

  • (\\[^\]+\\[^\]+$) matches the portion having two \s till end, and put in captured group 2, the .* preceding this matches everything in between the first and second captured groups

  • In the replacement we have used the two captured groups

POSIX-ly:

sed 's#\(.*\\src\).*\(\\[^\]\+\\[^\]\+$\)#\1\2#' file.txt

Example:

% cat file.txt
..\..\src\modules\core\abc\abc.cpp
..\..\src\modules\core\something\xyz\xyz.cpp
..\..\src\other_modules\new_core\something\pqr\pqr.cpp
..\..\src\other_modules\new_core\something\pqr\abc.cpp

% sed -E 's#(.*\\src).*(\\[^\]+\\[^\]+$)#\1\2#' file.txt
..\..\src\abc\abc.cpp
..\..\src\xyz\xyz.cpp
..\..\src\pqr\pqr.cpp
..\..\src\pqr\abc.cpp
4
  • could you let me know why did you use sed -E Oct 4, 2016 at 5:39
  • 1
    @phoenix The -E option lets us to use ERE (Extended RegEx) patterns, otherwise we have to use BRE (Basic RegEx) patterns. In practice, we then need to escape the ()s in the captured groups, + token, and also {}, ? if present and used as Regex token, otherwise these will be treated literally. Without -E: sed 's#\(.*\\src\).*\(\\[^\]\+\\[^\]\+$\)#\1\2#' file.txt
    – heemayl
    Oct 4, 2016 at 5:43
  • 1
    @phoenix, not only that, but \+ (as in above comment) is much less portable than using -E and +. -E is fairly standard.
    – Wildcard
    Oct 4, 2016 at 5:50
  • can also use sed -E 's#(.*\\src).*((\\[^\]+){2})$#\1\2#' to avoid repeating regex pattern, can easily change number as well if requirement changes....
    – Sundeep
    Oct 4, 2016 at 7:36
0

Alternate solutions:

With GNU grep and paste

grep extracts the two patterns .*\\src or (\\[^\]+){2}$ and prints them on separate lines. The output is then combined using paste

$ grep -oE '.*\\src|(\\[^\]+){2}$' ip.txt | paste -d '' - -
..\..\src\abc\abc.cpp
..\..\src\xyz\xyz.cpp
..\..\src\pqr\pqr.cpp
..\..\src\pqr\abc.cpp

With perl

$ perl -pe 's/.*\\src\K.*(?=(\\[^\\]+){2}$)//' ip.txt 
..\..\src\abc\abc.cpp
..\..\src\xyz\xyz.cpp
..\..\src\pqr\pqr.cpp
..\..\src\pqr\abc.cpp

Here the text between the patterns .*\\src and (\\[^\\]+){2}$ is deleted by making use of positive lookarounds

1
  • 1
    Thank you Sundeep, learnt grepping two patterns and also paste command. :) Oct 4, 2016 at 8:23
0

Create a file with data

-rwxr-xr-x. 1 sasi   webApp  190 Oct  4 13:42 file.txt

Execute below command

[sasi@localhost temp]$ sed -E 's#(.*\\src).*(\\[^\]+\\[^\]+$)#\1\2#' file.txt
..\..\src\abc\abc.cpp
..\..\src\xyz\xyz.cpp
..\..\src\pqr\pqr.cpp
..\..\src\pqr\abc.cpp
[sasi@localhost temp]$
[sasi@localhost temp]$
[sasi@localhost temp]$
7
  • it would be helpful if you format your answer, also I see an error in the regular expression that you are suggesting Oct 4, 2016 at 5:50
  • sed -E 's#(.\src).([^]+[^]+$)#\1\2#' file.txt
    – sasikaran
    Oct 4, 2016 at 5:56
  • run above command with file.txt
    – sasikaran
    Oct 4, 2016 at 5:58
  • that doesn't work Oct 4, 2016 at 6:06
  • which OS using ? Bec i can get a result with same command ..
    – sasikaran
    Oct 4, 2016 at 6:50
0

Why bash this with regex? Path munging doesn't require regular expressions; OS kernels don't use regexes to follow paths.

With Awk, we just use backslash as a separator and components become fields:

awk 'BEGIN { FS = OFS = "\\" } { print $1, $2, $3, $(NF-1), $NF }'

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.