Find all matches of two-character string in a text file and swap them Find all matches of two-character string in a text file and swap them shell shell

Find all matches of two-character string in a text file and swap them


sed supports backreferences to capture groups defined in the regex argument of s calls in the replacement argument (using bash here-string syntax (<<<) for brevity):

$ sed -E 's/([.?!;:])_/_\1/g' <<<'On this _line,_ I show an example. !_'On this _line,_ I show an example. _!

\1 refers to the 1st capture group ((...)) in the regex.

Note that -E was used to enable support for extended regexes, which use modern syntax - both GNU sed and BSD/macOS sed support this option.


Generally, you don't need sed's -e option, unless you're passing the sed script in multiple parts, in which case each part must be -e-prefixed.

As for in-place updating of the input file:

-ie probably does not do (exactly) what you want: while it does update the input file (by replacing it with a new file with updated content), it creates a backup file with suffix e, because e is interpreted as option -i's option-argument.

If the intent is not to create a backup file, the syntax - sadly - differs based on what sed implementation you're using:

  • GNU sed: sed -i ...

    • -i must not be directly followed by any other options / characters.
  • BSD/macOS sed: sed -i '' ...

    • -i must be followed by '' as the next, separate argument.


Here is one way you can do it with a single line:

sed  's/\([^\w\s]\)\(_\)/\2\1/g' test.txt

Essentially, you are looking for two characters, and swapping them.

s/ - This starts the substitution

\( \) - This escapes the parentheses. Gotta do this, even if its ugly.

\s a whitespace character

[ ] sets up a character class

^ negates at the first position inside a character class

[^\w\s] all characters that are not letters or whitespace (aka punctuation)

Then we get to the next match, an underscore. We make this the second item to check for

\(_\) - First, find punctuation and mark that as match number 1, then find an underscore, right next to it, and mark it as match number 2.

/\2\1/ - Now, swap matches 1 and 2

/g - do this globally.

The end. Now, you can output this to another file, or use another sed modifier, (the -i switch), to change the file inline.