Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Technically speaking, this isn't a LaTeX specific question, but there are so many useful tools in LaTeX, and I'm using it to solve a LaTeX problem, so I thought here would be a good place to ask.

I have a rather large document with lots of math notation. I've used |foo| throughout to indicate the absolute value of foo. I'd like to replace every instance of |foo| with \abs{foo}, so that I can control the notation via an abs macro I define.

Does anybody know of a way to do this? Possibly using regular expressions of some sort? Are there editors with tools built in for this?

share|improve this question
3  
It may be possible with the editor you're already using. What is it? –  egreg May 24 '13 at 21:50
1  
I'm in TexMaker right now. –  jmite May 24 '13 at 21:54
2  
Is Edit -> Replace what you're looking for? It has forward/backward search and is also case sensitive. –  Count Zero May 24 '13 at 22:00
3  
It seems that the regex search and replace capabilities of Texmaker are not sufficient. With TeXStudio it seems possible with \|([a-z]*)\| in the "Find" field and \abs{\1} in the Replace field (with Regexp checked). –  egreg May 24 '13 at 22:11
1  
@jmite: It depends whether you have spaces around the |s. You could run the search to replace once the opening | and once the closing |. But I gather from your comment that this is not the case... My bad! :) ...and +1 –  Count Zero May 24 '13 at 22:55
show 7 more comments

6 Answers

up vote 13 down vote accepted

Option 1: sed

The stream editing tool, sed, would be a natural first choice, but the problem is that sed can't match non-greedy regular expressions.

We need a non-greedy regular expression here- to clarify why, let's consider

sed -r 's/|(.*)|/\\abs{\1}/g' myfile.tex

If we apply this substitution to a file that contains something like

$|a|+|b|\geq|a+b|$

then we'll get

$\abs{a|+|b|\geq|a+b}$

which is clearly not what we want- regular expression matches like this are greedy by default.

To make it non-greedy, we typically use .*?, but the problem is that sed does not support this type of match. Happily (thanks Hendrik) we can use the following instead

sed -r 's/\|([^|]*)\|/\\abs{\1}/g' myfile.tex

Once you're comfortable that it does what you want, you can use

sed -ri.bak 's/\|([^|]*)\|/\\abs{\1}/g' myfile.tex

which will overwrite each file, and make a back up first, myfile.tex.bak

Option 2: perl

We could, instead, use a little perl one-liner:

perl -pe 's/\|(.*?)\|/\\abs{\1}/g' myfile.tex

When you're sure that you trust it is working correctly, you can use the following to overwrite myfile.tex

perl -pi -e 's/\|(.*?)\|/\\abs{\1}/g' myfile.tex

You can replace myfile.tex with, for example, *.tex to operate on all the .tex files in the current working directory.

Details of perl's switches are discussed here (for example): http://perldoc.perl.org/perlrun.html#Command-Switches

share|improve this answer
4  
Did you test your sed code? Note that you need \( instead of (! Moreover, note that it's easy to make the sed version non-greedy: just use [^|]* instead of .*. –  Hendrik Vogt May 25 '13 at 6:01
    
@HendrikVogt thanks for the feedback. If you use sed -r .... then you gain access to ( and ) without having to escape them- sorry if I didn't make that clear. I have added your solution, thanks! –  cmhughes May 25 '13 at 17:19
2  
I would strongly suggest to use sed -ri .bak to have sed automatically create backup files for the in-place edits. Also you might consider editing the last paragraph into the first one regarding the sed-greedy issue. As it stands now, it might be confusing for users who are not used to these kind of tools. –  Daniel May 25 '13 at 17:28
    
@Daniel thanks- have reordered –  cmhughes May 25 '13 at 17:35
    
@cmhughes: Ah, thanks, I didn't know about the -r option! –  Hendrik Vogt May 25 '13 at 19:08
show 5 more comments
\documentclass{article}

\begin{document}

\def\abs#1{|#1|} % Let us assume that it is your definition

$\abs{c+d}$

\catcode`\|=\active
\def|#1|{\abs{#1}}

$|a|+|b|\geq|a+b|$

\end{document}

We assume that |'s are in pairs. Then the first one becomes definition, the second -- end of an argument. It seems, however, that, as for all the other solutions, a manual correction will be necessary.

share|improve this answer
    
Is there any way to output the altered source file, with the replacements made? If not, this solution will still do in a pinch. Thanks! –  jmite May 24 '13 at 22:35
    
@jmite Do you mean such transformation of a file that all but the changed defintion remains unchanged? If yes: it seems to be difficult. –  Przemysław Scherwentke May 24 '13 at 22:42
    
Yes, that's what I mean. –  jmite May 24 '13 at 22:43
    
This might clash with other usages of | in math mode; it would fail immediately at \left|. –  egreg May 25 '13 at 9:53
    
@egreg Yes, it is rather a temporary solution. For some reason OP cannot change |'s manually immediately, which seems to be the safest solution. –  Przemysław Scherwentke May 25 '13 at 12:11
add comment

We're looking for long answers that provide some explanation and context. Don't just give a one-line answer; explain why your answer is right, ideally with citations. Answers that don't include explanations may be removed.

You can use Vim to achieve this. Please also have a look here.

In your case, the command would be

%s:\(|\)\(.\{-}\)\(|\):\\abs{\2}:gc

Brief explanation:

%s - says it is the substitute command over the entire file

\( | \) matches the occurrence of | and references it as \1 in the match

\( .\{-} \) matches anything between two instances of | but makes it as small as possible, it references this chunk as \2 which we later use to put it back

\( | \) again matches another occurrence of | and references it as \3 in the match

: signals end of regex

\\abs{\2} is what you want to replace it with, where \2 is the text between the two |

:gc says everywhere in the line, and muse be confirmed.

You must confirm every replacement because this string is not perfect. Reason? If there is an odd number of | in a line then it cannot tell which two instances of | form the text in between that you are taking the absolute value of. However, it will not skip over lines, so you are relatively protected.

share|improve this answer
1  
In vim I sometimes find it useful to record a macro for such cases. qa to start recording a macro labeled "a"; /|<CR> to go to the next occurrence of "|"; s\abs{<ESC> to replace "|" with "\abs"; /|<CR>r} to go to next "|" and replace with "}"; then "q" to stop recording. Then I just hit 100@a and the macro is performed 100 times or until no more occurrences of "|" can be found. –  Kallus May 27 '13 at 16:00
add comment

You can also try WinEdt. It has a very well implemented 'search and replace' feature.

In your case, search for (with regex on)

\|\(0*\)\|

and replace with

\\abs\{\0\}

The whole replacement can be done in one step.

You can also choose (with a different regex) to make the replacement ONLY inside math environments.

share|improve this answer
add comment

This post is inspired by cmhughes proposed solutions. His post is one of the most interesting posts on TeX editing which I have ever read. I just spent 2 hours trying to produce nawk solution which actually doesn't exist (see below).

Option 3: Gawk

This solution is provided by Hendrik Vogt

gawk '{print gensub(/\|([^|]*)\|/, "\\abs{\\1}", "g", $0)}'

Option 4: Python

import sys
import re

file = sys.stdin.readlines()
for line in file:
    newline = re.sub('\|(.*?)\|', '\\\\abs{\\1}', line)
    sys.stdout.write(newline)

Why is AWK (nawk) the wrong tool for the above problem?

AWK doesn't support non-greedy regular expressions which is to be expected since it is sed's cousin but even worse AWK regular expression does not capture its groups. Even if AWK was supporting group capturing we would be in trouble as backreferences cannot be used inside character classes and we use character classes to achieve non-greediness in AWK.

A simple AWK script

NR>0{
gsub(/\|([^|]*)\|/,"\\abs{\1}")
print
}

Applied to file

$|abs|$ so on and so fourth
$$|a|+|b|\geq|a+b|$$
who is affraid of wolf $|abs|$

will unfortunately produce

$\abs{}$ so on and so fourth
$$\abs{}+\abs{}\geq\abs{}$$
who is affraid of wolf $\abs{}$
share|improve this answer
3  
Try gensub: awk '{print gensub(/\|([^|]*)\|/, "\\abs{\\1}", "g", $0)}' –  Hendrik Vogt May 27 '13 at 10:52
2  
@PredragPunosevac This is not a discussion forum. You surely can discuss alternatives, so long as you provide a solution to the problem. Martin's comment aims to keep the site in line with its stated scope. Your non-answer might be a legitimate question on SuperUser.com or Unix.sx –  egreg May 27 '13 at 12:53
1  
@Martin, egreg, Predrag: In fact, there is a long history both here and especially on SO of extended commentary being put in answers because it (i) furthers the question, and (ii) doesn't fit in a comment. Maybe it's best if we move this discussion over to Meta? –  Charles Stewart May 27 '13 at 12:55
1  
@PredragPunosevac: I'm sorry that you took this personally. My comments are not meant negatively. I'm just doing my job as a moderator. Also these comments are also not targeted against specific people, but in fact are created by moderators as reaction on flags added by other users. For example, this post has been flagged as "not an answer" by two users. This site has specific rules and does not work like a forum. Answer posts are for answers(=solutions) to the question. Discussions are not wanted on this site. Please have another look at the FAQ –  Martin Scharrer May 27 '13 at 13:40
1  
@Predrag: Thanks for pointing this out. You're right, it has to be gawk, which my machine uses if I ask for awk. Unfortunately I can't edit my comment. –  Hendrik Vogt May 27 '13 at 17:11
show 9 more comments

On Mac OSX I use TextWrangler, it is an editor which—among its many features—has a Search/Replace facility able to handle regular expressions. For example I can choose:

enter image description here

The first time you click “Next” and then you can keep clicking “Replace & Find” to go through your document, validating each match before a replacement is made. This will lower the potential of messing up your document because of using an incorrect regular expression or making unintended replacements. And with infinite “Undo” you'll always be safe.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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