I am thinking how you can replace [^\]% marks but not \% marks in the sed -command of this answer. I think look-behind is not necessary.

My current Sed command but I think Perl is a must here

cat something | sed 's#%.*</#</#'                

which removes also everything after the % sign i.e. all comments in LaTeX but not percentage values.

My unsuccessful Perl attempt

cat something | perl 's#[^\]%.*</#</#'

where I do not know how I make Perl to take the standard output of cat.


Protocol of pre-eclampsia
Monitoring in 90\% cases

Antihypertensives when % this is a comment, please, remove me!
$SBP/DBP > 160/110$; slowly.     

Desired output

Protocol of pre-eclampsia
Monitoring in 90\% cases

Antihypertensives when
$SBP/DBP > 160/110$; slowly.     

How can you replace % signs but not \%? If you can do this by Sed, please, comment.

  • 1
    Please include an example of your input and your desired output. What should be done with \%%%%%%%%? How does < come into it? – terdon May 30 '15 at 10:13
  • I added an example input and output. \%%%%%... is a comment so everything after second % should be removed. – Léo Léopold Hertz 준영 May 30 '15 at 10:25
  • @terdon It should be replaced by \%, that's what TeX does. However, things like \\% and \\\% are more problematic. – lcd047 May 30 '15 at 10:25
  • @lcd047 I was asking because the OP's code would replace a %.*< with #< and I wasn't sure if he wanted comments removed or true replacement of all matching characters. I still don't know why there's s < in the code. – terdon May 30 '15 at 10:27
  • 2
    If this is about TeX comments pleas note that the behaviour of whitespace added when replacing % with a newline could pose a problem – Hagen von Eitzen May 30 '15 at 15:44

Like many, if not most, text parsing tools, perl can take input from the command line, there's no need for cat. You just need -e which lets you pass a script as a command line parameter and -n which means "run the script on each line of input". ALternatively, you can use the -p switch which means "run the script on each line of input, then print that line". These two commands are equivalent (but the second is a classic useless use of cat, use the first) :

perl -pe 's/foo/bar/' file
cat file | perl -pe 's/foo/bar/'

Now, if I understand correctly, you want to delete all LaTeX comments (though that's not what your question states). If so, a lookbehind is the easiest way:

perl -pe 's/(?<!\\)%.*//' file 

Your regex should also work, you just need to keep the character you matched before the % and escape the backslash:

perl -pe 's/(^|[^\\]+)%.*/$1/' file

You can do the same thing with GNU sed:

sed -r 's/(^|[^\\])%.*/\1/' file
  • This replaces all % signs with #, while it should just delete comment signs (%) and leave \% signs alone. – Léo Léopold Hertz 준영 May 30 '15 at 10:29
  • 1
    @Masi OK, see update. But please edit your question and clarify what you need. – terdon May 30 '15 at 10:37
  • perl -pe 's/([^\\]*)%.*/$1/' test.tex gives ... Monitoring in 90\ Antihypertensives when ... so no percent sign. My Perl version is 5.18.2. – Léo Léopold Hertz 준영 May 30 '15 at 10:44
  • @Masi well, yes, that's what you asked for. That's what you show in your desired output, so that's what I gave you. – terdon May 30 '15 at 10:48
  • Desired output is updated in the body, sorry for confusion. It should be Monitoring in 90\% cases by just removing the delete me comment. – Léo Léopold Hertz 준영 May 30 '15 at 10:50

If you just want to replace what follows % but not what follows \%, in Perl, the easiest way is with a negative lookbehind: match %.* only if it isn't preceded by a backslash.

perl -pe 's/(?<!\\)%.*//'

However this won't match something like Hello world.\\%wibble. For that, you need to check that the % is preceded by an even number of backslashes. You can't do that with a lookbehind, because Perl's lookbehinds only support fixed-length patterns. Instead, match the backslashes in the regexp, and use a lookbehind to ensure that the regexp captures them all.

perl -pe 's/(?<!\\)((?:\\\\)*)%.*/$1/'

You can do that with tools that don't support lookbehind as well. In this case, you'll need to either use a tricky succession of replacement commands or match the backslashes and copy them to the replacement text.

sed -e 's/^\(\(\\\\\)*\)%.*/\1/' -e 's/\([^\\]\(\\\\\)*\)%.*/\1/'

Note that if you're processing a LaTeX document, there are other percent signs that might need to stay, for example in verbatim blocks. That can't be done with regexps alone.

  • 1
    Is there a wobble in your wibble in the sed version? I don't get the expected results. If I uderstand it correctly, it is intended to delete zero to all prefixed '\\' pairs + % + the rest of the line such as in the lines – %wibble x0, \\%wibble x2, \\\\%wibble x4, x0 \\%wbble, x2 \\%wbble, x4 \\\\%wobble. If that is so, then I believe this works: sed -e 's/^\(\\\\\)*%.*//' -e 's/\([^\\]\)\(\\\\\)*%.*/\1/' – Peter.O May 31 '15 at 5:25
  • @Peter.O Oops, yes, both the perl and the sed versions were missing some bits. Your sed version removes backslashes. – Gilles 'SO- stop being evil' May 31 '15 at 12:19

A common idiom to replace unescaped characters in perl is with:

$ printf '%s\n' '% \% \\% \\\%' | perl -pe 's/(\\.)|%/$1||"<replacement>"/ge'
<replacement> \% \\<replacement> \\\%

So to remove everything starting from an unescaped %:

perl -pe 's/(\\.)|%.*/$1/g'

If your sed supports -E (FreeBSD/GNU):

sed -E 's/(\\.)|%.*/\1/g'

Or with GNU sed:

sed 's/\(\\.\)\|%.*/\1/g'

Where the alternation RE operator is not available (as in standard basic RE), you could often use \{0,1\} instead:

sed 's/\(\(\(\\.\)\{0,1\}[^\\%]*\)*\)\(%.*\)\{0,1\}/\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.