# Utility to Strip Comments from LaTeX Source

I've a LaTeX source.
I'm ready %for submission
%But first I would like
to strip its comments.

So I hope there are
100\% auto
ways to get this done.

\begin{comment}
Because there are subtle ways to mess it up.
\end{comment}


Is there a utility which will eliminate all these comments?

Yes, I could do it by hand, but that seems needlessly laborious, has the potential for mistakes, and makes maintenance difficult. I could also use sed, but there's a potential for mistakes. Besides, it is an axiom of the whole GNU/Linux thing that if you can think of it, someone's probably already made a utility for it.

• should the text "Because there ..." be printed?
– user2478
Nov 21, 2012 at 12:20
• No, it's in a comment environment from the verbatim package. Nov 21, 2012 at 12:29
• ah, I see, you want to delete the %blabla ..
– user2478
Nov 21, 2012 at 12:32
• For motivation as to why one might want to do this, see twitter.com/overheardonaph Oct 11, 2015 at 19:26
• See this answer for using regular expressions (+ a step-by-step process detailing how to implement them in your IDE — even if it does not support regex lookaround). Sep 21, 2018 at 12:38

To remove all the comments from a LaTeX file, one can use arxiv-latex-cleaner. Actively maintained, 1.2k GitHub stars, written in Python but no need to know Python.

# latexpand

(distributed with TeXLive)

latexpand is a tool to expand include/input directives which also removes comments. It can also be tweaked to keep end-of-line comments because sometimes they are meaningful. In a second step remove lines with only %:

$latexpand --empty-comments mytexfile.tex > mytexfile-stripped.tex  Remove lines with only % and whitespace: $ sed -i '/^\s*%/d' mytexfile-stripped.tex


and squeeze blank lines (cat -s might have problems with window line endings and on os solaris -s has a different meaning)

$cat -s mytexfile-stripped.tex | sponge mytexfile-stripped.tex  (sponge is part of moreutils from https://joeyh.name/code/moreutils/) • But latexpand translates all includes into the included file. May 2, 2017 at 21:03 • @RaffiKhatchadourian There is an option --keep-includes. Sep 22, 2017 at 16:32 • This is a very nice and handy solution! I have directly put it into a script, see my answer below, because it seems I cannot add code in a comment. Mar 29, 2018 at 7:20 • I was getting an "invalid command" message on OSX when I ran the above sed command. I changed sed to gsed and that fixed the problem. Sep 13, 2020 at 23:16 • Upvote from me because it is built-in texlive, no need for additional scripts. Jun 24, 2021 at 9:35 I'm not sure how to do this. So, I'm posting a new solution. The code I posted yesterday will eat comments from within a verbatim environment. Here's a new example file to be cleaned: I've a LaTeX source. I'm ready %for submission %But first I would like to strip its comments. So I hope there are 100\% auto ways to get this done. \begin{comment} Because there are subtle ways to mess it up. \end{comment} \begin{verbatim} next two lines should not be lost % don't lose this line % this line should stay too \end{verbatim}  According to the verbatim package documentation verbatim and comment environments should not be nested. The following code (similar to what I posted yesterday) will not eat commented lines that appear within a verbatim environment. Here is the corrected Perl code: #!/usr/bin/perl use strict 'vars'; &MAIN(@ARGV); sub MAIN { my ($filehandle) = @_;

open FILE, "<$filehandle"; my @doc = <FILE>; close FILE; &removeComments(\@doc); foreach my$line ( @doc ){
print $line; } return 1; } sub removeComments { my ($docarray) = @_;

my $isCommentEnvironment = "no"; my$isVerbatimEnvironment = "no";

my @newdoc;

foreach my $line ( @{$docarray} ){
$isVerbatimEnvironment = "yes" if ($line =~ /^\\begin{verbatim}/ );
$isCommentEnvironment = "yes" if ($line =~ /^\\begin{comment}/ );
if ( ($isVerbatimEnvironment eq "no") && ($isCommentEnvironment eq "no") ){
next if ($line =~ /^%/); ## Temporarily replace "%" that you want to keep with a dummy string ## that does not appear elsewhere in your document. Then, remove remainder ## of lines that still contain "%". if ($line =~ /\\%/){
$line =~ s/\\%/TMP::PERCENT/g;$line =~ s/%.*//;
$line =~ s/TMP::PERCENT/\\%/g; } else { ## do not remove trailing % marking NO SPACE in LaTeX:$line =~ s/%.*//;
$line =~ s/\s*%.+//; } push @newdoc,$line;
}
push @newdoc, $line if ($isVerbatimEnvironment eq "yes" );

$isVerbatimEnvironment = "no" if ($line =~ /^\\end{verbatim}/ );
$isCommentEnvironment = "no" if ($line =~ /^\\end{comment}/ );
}

@{$docarray} = @newdoc; return 1; }  • I think the standard convention here on TeX.SX would be to add this as an alternative in the first solution you provide, by using the edit button. Nov 22, 2012 at 16:26 • How can I output the code shown in the console to a file? – Jörg Dec 3, 2012 at 13:45 • The code provided by A.Ellett worked great for me, with a notable exception: the '%' sign at the end of the line should not be removed, as they have the side-effect of "eating" the white space represented by the newline. As a consequence, I used a different regex for the substitution: s/\s*%.+//. Jul 5, 2013 at 22:41 • I think also the line next if ($line =~ /^%/); had better be next if ($line =~ /^\s*%/);, else the output will contain blank lines that aren't present in the original Nov 19, 2014 at 4:18 • I think the current version (with and without @D.Savitt hint) makes from BLABLA%TEST this BLABLA. It would be nice to have some option to get this BLABLA% i.e. to still prevent 'eating' the white space. Mar 24, 2015 at 9:28 It can be done using the Python ply.lex module to write a simple tokenizer: import ply.lex, argparse, io #Usage # python stripcomments.py input.tex > output.tex # python stripcomments.py input.tex -e encoding > output.tex def strip_comments(source): tokens = ( 'PERCENT', 'BEGINCOMMENT', 'ENDCOMMENT', 'BACKSLASH', 'CHAR', 'BEGINVERBATIM', 'ENDVERBATIM', 'NEWLINE', 'ESCPCT', ) states = ( ('linecomment', 'exclusive'), ('commentenv', 'exclusive'), ('verbatim', 'exclusive') ) #Deal with escaped backslashes, so we don't think they're escaping %. def t_ANY_BACKSLASH(t): r"\\\\" return t #One-line comments def t_PERCENT(t): r"\%" t.lexer.begin("linecomment") #Escaped percent signs def t_ESCPCT(t): r"\\\%" return t #Comment environment, as defined by verbatim package def t_BEGINCOMMENT(t): r"\\begin\s*{\s*comment\s*}" t.lexer.begin("commentenv") #Verbatim environment (different treatment of comments within) def t_BEGINVERBATIM(t): r"\\begin\s*{\s*verbatim\s*}" t.lexer.begin("verbatim") return t #Any other character in initial state we leave alone def t_CHAR(t): r"." return t def t_NEWLINE(t): r"\n" return t #End comment environment def t_commentenv_ENDCOMMENT(t): r"\\end\s*{\s*comment\s*}" #Anything after \end{comment} on a line is ignored! t.lexer.begin('linecomment') #Ignore comments of comment environment def t_commentenv_CHAR(t): r"." pass def t_commentenv_NEWLINE(t): r"\n" pass #End of verbatim environment def t_verbatim_ENDVERBATIM(t): r"\\end\s*{\s*verbatim\s*}" t.lexer.begin('INITIAL') return t #Leave contents of verbatim environment alone def t_verbatim_CHAR(t): r"." return t def t_verbatim_NEWLINE(t): r"\n" return t #End a % comment when we get to a new line def t_linecomment_ENDCOMMENT(t): r"\n" t.lexer.begin("INITIAL") #Newline at the end of a line comment is stripped. #Ignore anything after a % on a line def t_linecomment_CHAR(t): r"." pass lexer = ply.lex.lex() lexer.input(source) return u"".join([tok.value for tok in lexer]) def main(): parser = argparse.ArgumentParser() parser.add_argument('filename', help = 'the file to strip comments from') parser.add_argument('--encoding', '-e', default='utf-8') args = parser.parse_args() with io.open(args.filename, encoding=args.encoding) as f: source = f.read() print(strip_comments(source)) if __name__ == '__main__': main()  Alternately, here is a Gist. This correctly handles comments which directly follow a double backslash, such as Line text \\%comment-text  The first answer above seems to handle this incorrectly (as if the percent sign were escaped), though I don't have sufficient reputation to comment. • It indeed seems to handle more subtleties (and is easier to extent). Jan 18, 2016 at 10:56 • there is a bug though. t_ANY_BACKSLASH will cause newlines like \\[2mm] in a comment environment to be returned. Why do you need ANY? Sep 2, 2019 at 20:07 The following Perl script should do the trick: it expects to receive the name of the file you want stripped of comments and prints to console the modified document. #!/usr/bin/perl use strict 'vars'; &MAIN(@ARGV); sub MAIN { my ($filehandle) = @_;
open FILE, "<$filehandle"; my @doc = <FILE>; close FILE; &removeComments(\@doc); foreach my$line ( @doc ){
print $line; } return 1; } sub removeComments { my ($docarray) = @_;
my $isCommentEnvironment = "no"; my @newdoc; foreach my$line ( @{$docarray} ){$isCommentEnvironment = "yes" if ( $line =~ /^\\begin{comment}/ ); if ($isCommentEnvironment eq "no" ){
next if ($line =~ /^%/); ## Temporarily replace "%" that you want to keep with a dummy string ## that does not appear elsewhere in your document. Then, remove remainder ## of lines that still contain "%". if ($line =~ /\\%/){
$line =~ s/\\%/TMP::PERCENT/g;$line =~ s/%.*//;
$line =~ s/TMP::PERCENT/\\%/g; } else {$line =~ s/%.*//;
}
push @newdoc, $line; }$isCommentEnvironment = "no" if ( $line =~ /^\\end{comment}/ ); } @{$docarray} = @newdoc;
return 1;
}


The only thing I'm uncertain about is any contraints on using \begin{comment} and \end{comment}. For example, I don't know whether---like with the verbatim environment---you are restricted on what else may appear on those lines.

pandoc can do this. Running:

pandoc latex-comments.tex -o latex-stripped.tex


Converts latex-comments.tex:

I've a LaTeX source.
I'm ready %for submission
%But first I would like
to strip its comments.

So I hope there are
100\% auto
ways to get this done.

\begin{comment}
Because there are subtle ways to mess it up.
\end{comment}

\begin{verbatim}
next two lines should not be lost
% don't lose this line
% this line should stay too
\end{verbatim}


to latex-stripped.tex:

I've a LaTeX source. I'm ready to strip its comments.

So I hope there are 100\% auto ways to get this done.

Because there are subtle ways to mess it up.

\begin{verbatim}
next two lines should not be lost
% don't lose this line
% this line should stay too
\end{verbatim}


It does remove the comment environment, while leaving the contents unaltered. I'm not sure how these environments are used, but it may be possible to keep them with appropriate config settings in pandoc.

• Unfortunately, > pandoc latex-comments.tex -o latex-stripped.tex does more than just strip the comments. It also removes most Latex commands and the preamble.
– user60642
Aug 13, 2014 at 7:44
• @KurtKenner with the -s option it will recreate a stand-alone tex document, with preamble. But I'm not sure how well it incorporates the existing preamble into this :( Aug 13, 2014 at 14:44

(Posting the following as an answer rather than a comment to Ellett's Nov 22 '12 at 15:23 answer because the line breaks in code don't show up in comments.) Remarking that latex code of the following form:

here is some text%comment %comment , and here is some more text

is problematic because it compiles as here is some text, and here is some more text whereas the stripped code:

here is some text , and here is some more text

compiles as here is some text , and here is some more text.

(This goes to the same sort of issue about which user1366204 commented.)

I really like the answer from @Hotschke and put it into a script with the name latex_cleanup.sh.

#!/bin/bash
# Removing all comments from a latex source and compile all included source files into one latex document.
# Taken from https://tex.stackexchange.com/questions/83663/utility-to-strip-comments-from-latex-source
#
# First argument source
# Second arumgent destination
# Example latex_cleanup.sh mytexfile.tex mytexfile_stripped.tex

latexpand --empty-comments "$1" > "$2"
sed -i '/^\s*%/d' "$2" cat -s "$2" | sponge "$2"  This answer is extended from @Adam Merberg's answer. Modifications: 1. The original snippet neglect that sometimes \makeatletter \makeatother block need the % at the end of some lines. Removing those % will result in compilation failure. For example: \makeatletter \def\alloc@#1#2#3#4#5% {\ifnum\count1#1<#4% make sure there's still room \allocationnumber\count1#1 \global\advance\count1#1\@ne \global#3#5\allocationnumber \wlog{\string#5=\string#2\the\allocationnumber}% \else\ifnum#1<6 \def\etex@dummy@definition{}% <-- code added \begingroup \escapechar\m@ne \expandafter\alloc@@\expandafter{\string#2}#5% \else\errmessage{No room for a new #2}\fi\fi } \makeatother  The % at the end of the 2nd line must be preserved. 1. The original snippet will trim the ending \n if there is a line comment, which consequently merge the next line to current line. For example, \usepackage{foo} % foo package \usepackage{bar} \usepackage{baz}  will result in \usepackage{foo} \usepackage{bar} \usepackage{baz}  The modified snippet: import ply.lex, argparse, io # modified from https://gist.github.com/amerberg/a273ca1e579ab573b499 #Usage # python stripcomments.py input.tex > output.tex # python stripcomments.py input.tex -e encoding > output.tex # Modification: # 1. Preserve "\n" at the end of line comment # 2. For \makeatletter \makeatother block, Preserve "%" # if it is actually a comment, and trim the line # while preserve the "\n" at the end of the line. # That is because remove the % some time will result in # compilation failure. def strip_comments(source): tokens = ( 'PERCENT', 'BEGINCOMMENT', 'ENDCOMMENT', 'BACKSLASH', 'CHAR', 'BEGINVERBATIM', 'ENDVERBATIM', 'NEWLINE', 'ESCPCT', 'MAKEATLETTER', 'MAKEATOTHER', ) states = ( ('makeatblock', 'exclusive'), ('makeatlinecomment', 'exclusive'), ('linecomment', 'exclusive'), ('commentenv', 'exclusive'), ('verbatim', 'exclusive') ) # Deal with escaped backslashes, so we don't # think they're escaping % def t_BACKSLASH(t): r"\\\\" return t # Leaving all % in makeatblock def t_MAKEATLETTER(t): r"\\makeatletter" t.lexer.begin("makeatblock") return t # One-line comments def t_PERCENT(t): r"\%" t.lexer.begin("linecomment") # Escaped percent signs def t_ESCPCT(t): r"\\\%" return t # Comment environment, as defined by verbatim package def t_BEGINCOMMENT(t): r"\\begin\s*{\s*comment\s*}" t.lexer.begin("commentenv") #Verbatim environment (different treatment of comments within) def t_BEGINVERBATIM(t): r"\\begin\s*{\s*verbatim\s*}" t.lexer.begin("verbatim") return t #Any other character in initial state we leave alone def t_CHAR(t): r"." return t def t_NEWLINE(t): r"\n" return t # End comment environment def t_commentenv_ENDCOMMENT(t): r"\\end\s*{\s*comment\s*}" #Anything after \end{comment} on a line is ignored! t.lexer.begin('linecomment') # Ignore comments of comment environment def t_commentenv_CHAR(t): r"." pass def t_commentenv_NEWLINE(t): r"\n" pass #End of verbatim environment def t_verbatim_ENDVERBATIM(t): r"\\end\s*{\s*verbatim\s*}" t.lexer.begin('INITIAL') return t #Leave contents of verbatim environment alone def t_verbatim_CHAR(t): r"." return t def t_verbatim_NEWLINE(t): r"\n" return t #End a % comment when we get to a new line def t_linecomment_ENDCOMMENT(t): r"\n" t.lexer.begin("INITIAL") # Newline at the end of a line comment is presevered. return t #Ignore anything after a % on a line def t_linecomment_CHAR(t): r"." pass def t_makeatblock_MAKEATOTHER(t): r"\\makeatother" t.lexer.begin('INITIAL') return t def t_makeatblock_BACKSLASH(t): r"\\\\" return t # Escaped percent signs in makeatblock def t_makeatblock_ESCPCT(t): r"\\\%" return t # presever % in makeatblock def t_makeatblock_PERCENT(t): r"\%" t.lexer.begin("makeatlinecomment") return t def t_makeatlinecomment_NEWLINE(t): r"\n" t.lexer.begin('makeatblock') return t # Leave contents of makeatblock alone def t_makeatblock_CHAR(t): r"." return t def t_makeatblock_NEWLINE(t): r"\n" return t # For bad characters, we just skip over it def t_ANY_error(t): t.lexer.skip(1) lexer = ply.lex.lex() lexer.input(source) return u"".join([tok.value for tok in lexer]) def main(): parser = argparse.ArgumentParser() parser.add_argument('filename', help = 'the file to strip comments from') parser.add_argument('--encoding', '-e', default='utf-8') args = parser.parse_args() with io.open(args.filename, encoding=args.encoding) as f: source = f.read() print(strip_comments(source)) if __name__ == '__main__': main()  The Gist for the code is here. I made a script based on the accepted answer that makes the change in-place, and backs up the old files. Check it out here: https://gist.github.com/aliparsai/46aad35544e128d72dee6cff44b2af21 #!/bin/bash ## Clean-up Comments from Latex Source Files ## Copyright (c) 2020 Ali Parsai ali@parsai.net ## ## Dependencies: sponge from moreutils, latexpand, sed, cat, mv, date, cp ## ## CAUTION: Use version control or backup your files before using this script. ## ## How to use: comment-cleanup.sh TEX_FILE(S) ## check_dependency () { if type$1 ; then
echo "==> $1 found." else date "==>$1 not found. Exitting."
exit 1
fi
}

check_dependency mv
check_dependency cp
check_dependency date
check_dependency cat
check_dependency sed
check_dependency sponge
check_dependency latexpand

dt=$(date '+%d/%m/%Y %H:%M:%S') for src in "$@"
do
backup="${src}.bak.${dt}"
cp -v "$src" "$backup"
done

for src in "$@" do dest="${src/.tex/-stripped.tex}"
latexpand --empty-comments --keep-includes "$src" > "$dest"
sed -i '/^\s*%/d' "$dest" cat -s "$dest" | sponge "$dest" mv "$dest" "$src" echo "==> Processed:$src"
done