# Removing a backslash from a character sequence

For indexing I wanted to write a macro \macroname that removes leading backslashes from macro names but leaves the names of environments untouched. That is

\macroname{\relax}   -->  relax
\macroname{itemize}  -->  itemize


where the --> is meant to be read as "expands to".

From my understanding, the following should work:

\newcommand\removebs[1]{\if#1\char92\else#1\fi}
\newcommand\macroname[1]{%
\expandafter\removebs\detokenize{#1}}


However, its outcome is

\macroname{\relax}   -->  \relax   % literally
\macroname{itemize}  -->  itemize


By chance, actually, I found out that if I change the catcode of the backslash to "other" during the definition of the \removebs macro, it works:

{
\catcode\|=0
|catcode|\=12
|global|def|removebs#1{|if#1\|else#1|fi}
}

\newcommand\macroname[1]{%
\expandafter\removebs\detokenize{#1}}


Why is that? From TeX by Topic I'd expect that the comparison made by \if is what Philipp Lehman calls "category code agnostic".

-
The problem is that you are comparing a backslash character token with the token \char and they are different; \char92 is an instruction for printing the character 92 from the currect font. –  egreg Jan 26 '12 at 8:00

Joseph has given a working solution. I'd like to explain what goes wrong with your code.

## First attempt

\newcommand\removebs[1]{\if#1\char92\else#1\fi}
\newcommand\macroname[1]{%
\expandafter\removebs\detokenize{#1}}


With \macroname{\relax} you get

\expandafter\removebs\detokenize{\relax}


and then (using • to separate tokens and <space> do denote a space token)

\removebs • \ • r • e • l • a • x • <space>


which becomes

\if • \ • \char • 9 • 2 • \else • \ • \fi • r • e • l • a • x • <space>


and the comparison is between a category code 12 backslash and the token \char, which of course leads to false.

The code \char92 are instruction to print character 92 from the current font.

One might correct the code by checking against a real category code 12 backslash:

\makeatletter
\newcommand{\removebs}[1]{\if#1\@backslashchar\else#1\fi}
\makeatother


but the space produced by \detokenize after \relax would remain.

## Second attempt

{
\catcode\|=0
|catcode|\=12
|global|def|removebs#1{|if#1\|else#1|fi}
}


This works because it just implements the check against a category code 12 backslash, but it's not necessary, as the token is already available as \@backslashchar in the LaTeX kernel.

An alternative way, without global definitions and category changes, can be

\begingroup\lccode\|=\\
\lowercase{\endgroup\def\removebs#1{\if#1|\else#1\fi}}


where the only token that is converted to its lowercase equivalent is | (to a backslash).

## Proposed code

% \makeatletter
% \newcommand{\removebs}[1]{\if#1\@backslashchar\else#1\fi}
% \makeatother
\begingroup\lccode\|=\\
\lowercase{\endgroup\def\removebs#1{\if#1|\else#1\fi}}
\newcommand{\macroname}[1]{\expandafter\removebs\string#1}


Since all you want is to get the argument letters, it doesn't matter if the first letter in \macroname{itemize} has category code 12.

-
The problem with an \if test here is that it will be true even if the input was not a control sequence to start with (for example if it is the string \ ). –  Joseph Wright Jan 26 '12 at 9:36
Why? With \macroname{itemize} we do \string itemize and so #1 is i (of category code 12). Of course, if one plays with \escapechar things are different. The empty string is disallowed, of course. –  egreg Jan 26 '12 at 11:38
I was thinking of {\catcode/=0/catcode\\=12/macroname{\}}, for example. –  Joseph Wright Jan 26 '12 at 11:41
In that case nothing would be printed; but the macro is intended for a specific application, so going into all details probably is only a nice exercise. The \  case leaves a space token in the input stream. –  egreg Jan 26 '12 at 11:47
Thanks for also answering my second question (without me asking it, of course) concerning the trailing space token. To keep things simple, I now went for \if#1\@backslashchar and replaced \detokenize with \string. –  5gon12eder Jan 26 '12 at 18:24

TeX works with tokens, and what you need to know is that a control sequence such as \foo is not tested by \if as a series of characters, but as a single 'unit'. There a a few primitives that will turn control sequences back into single tokens: \detokenize does this for a set of tokens, while \string does so for a single token. The latter is also available in all TeX versions (\detokenize requires e-TeX).

As you observe, detokenizing the input allows you to do a comparison to a category code 'other' \. An alternative approach would be to test if #1 is a control sequence, using the fact that \ifcat treats all (unexpanded) control sequences as identical. You can then use \string to convert to multiple tokens, and \@gobble to remove the very first character:

\documentclass{article}
\makeatletter
\newcommand{\removeabs}[1]{%
\ifcat\relax\noexpand#1%
\expandafter\expandafter\expandafter\@gobble\expandafter\string
\fi
#1%
}
\makeatother
\begin{document}
\removeabs{foo}
\removeabs{\bar}
\end{document}


The above is all fine as long as \escapechar is printable and not a space. Using some code from LaTeX3 'translated' back to primitives, you can set things up to be robust for all escape characters:

\documentclass{article}
\makeatletter
\newcommand{\removeabs}[1]{%
\ifcat\relax\noexpand#1%
\expandafter\removeabs@aux@i
\fi
#1%
}
\newcommand*{\removeabs@aux@i}{%
\romannumeral
\if\string\ \removeabs@aux@ii\fi
\expandafter\removeabs@aux@iii\string
}
\newcommand{\removeabs@aux@ii}{}
\long\def\removeabs@aux@ii#1\removeabs@aux@iii{%
-\number\fi\expandafter\z@
}
\newcommand{\removeabs@aux@iii}[1]{\z@}
\makeatother
\begin{document}
\removeabs{foo}
\removeabs{\bar}
\end{document}


(See the implementation of \cs_to_str:N in the LaTeX3 docs for what is going on here!)

-