Removing a backslash from a character sequence

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.


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!)