I would like to define a command that prints the first letter of its argument as superscript and the last two letters as subscribt. So if I type:


it should do the same as


such a command would save me hours of time, but I don't know how to do it

Edit: Sorry, I probably was not clear, the part in the middle can be everything. So only the first letter should be a superscript and the last two a subscript.

What I need is:

\anothernewcommand{a some text that can contain \textit{other commands} cd}

which than should do the same as

\textsuperscript{a} some text that can contain \textit{other commands} \textsubscript{cd}


I believe that a syntax such as \mynewcommand{a}{bc}{de} would be clearer. Anyway, I can offer two implementations that differ in the treatment of spaces after the superscript and before the subscript. Take your pick.


%\usepackage{xparse} % not needed for LaTeX 2020-10-01

  \textsuperscript{\tl_range:nnn { #1 } { 1 } { 1 } }
  \tl_range:nnn { #1 } { 2 } { -3 }
  \textsubscript{\tl_range:nnn { #1 } { -2 } { -1 } }

  \tl_set:Nn \l_tmpa_tl { #1 }
  \tl_replace_all:Nnn \l_tmpa_tl { ~ } { \c_space_tl }
  \textsuperscript{\tl_range:Nnn \l_tmpa_tl { 1 } { 1 } }
  \tl_range:Nnn \l_tmpa_tl { 2 } { -3 }
  \textsubscript{\tl_range:Nnn \l_tmpa_tl { -2 } { -1 } }


\textbf{Leading and trailing spaces are not kept}


\mynewcommandA{a some text that can contain \textit{other commands} cd}


\textbf{Leading and trailing spaces are kept}


\mynewcommandB{a some text that can contain \textit{other commands} cd}


enter image description here

Some more information. The function \tl_range:nnn takes three arguments where the first is some text, the second and third are integers that specify the range to extract; so {1}{1} extracts the first item (it can also be \tl_head:n, but I used the more complex function for uniformity), whereas {-2}{-1} specifies the last two items (with negative indices the extraction starts from the end); {2}{-3} specifies the range from the second item to the third starting from the right.

However, in order to keep spaces at the boundaries of the extracted parts, we have to first replace spaces with \c_space_tl, which will expand to a space, but is not trimmed by the extraction functions. The syntax of \tl_set:Nnn is the same, only the first argument has to be a tl variable.

  • That's perfect! Thank you so much! Using \ newcommand {a} {bc} {d} would be clearer, but actually a lot of work. Now I only have to change one command in the preamble, otherwise I would have to go through the entire document and put new brackets
    – M0M0
    Nov 9 '20 at 17:07
  • How might your code be extended to handle general utf8-encode strings, e.g., "öçäßüéà"?
    – Mico
    Nov 9 '20 at 21:57
  • @Mico Not easy at all with pdflatex
    – egreg
    Nov 9 '20 at 22:44
  • 1
    It may be useful to note that if anyone wishes to use your solution with general utf8-encoded character strings, all they have to do is switch from pdflatex to either lualatex or xelatex; no need to modify the workings of \mynewcommandA and \mynewcommandB. Of course, if a document contains lots of non-ascii utf8-encoded characters, they may already be using lualatex or xelatex (along with the fontspec package and a suitable OpenType font).
    – Mico
    Nov 10 '20 at 5:31

For sake of complexity, I show how to solve this problem at TeX primitive level:


\def\splitbuff #1#2{% #1: number of tokens from end, #2 data
                    % result: \buff, \restbuff
    \edef\buff{\detokenize{#2} }%
    \edef\buff{\expandafter}\expandafter\protectspaces \buff \\
    \bufflen=0 \expandafter\setbufflen\buff\end
    \advance\bufflen by-#1\relax
    \ifnum\bufflen<0 \errmessage{#1>buffer length}\fi
    \ifnum\bufflen>0 \edef\buff{\expandafter}\expandafter\splitbuffA \buff\end
    \else \let\restbuff=\buff \def\buff{}\fi
    {\endlinechar=-1 \scantokens\expandafter{\tmp}}%

\def\protectspaces #1 #2 {\addto\buff{#1}%
    \ifx\\#2\else \addto\buff{{ }}\afterfi \protectspaces #2 \fi}  
\def\afterfi #1\fi{\fi#1}

\def\setbufflen #1{%
    \ifx\end#1\else \advance\bufflen by1 \expandafter\setbufflen\fi}

\def\splitbuffA #1{\addto\buff{#1}\advance\bufflen by-1
    \ifnum\bufflen>0 \expandafter\splitbuffA
    \else \expandafter\splitbuffB \fi
\def\splitbuffB #1\end{\def\restbuff{#1}}

% --------------- \mynewcommand implementation:

\def\textup#1{$^{\rm #1}$}  \def\textdown#1{$_{\rm #1}$}
   \textup{#1}\splitbuff 2{#2}\buff \textdown{\restbuff}}

% --------------- test:


\mynewcommand{a some text that can contain {\it other commands} cd}

  • 1
    +1 for "for the sake of complexity". Good one! :-)
    – Mico
    Nov 10 '20 at 19:14

For the sake of variety, here's a LuaLaTeX-based solution. It sets up a Lua function which, in turn, makes use of Lua's string functions string.sub and string.len to accomplish its task. It also sets up a LaTeX "wrapper" macro called \mynewcommand, which expands its argument once before passing it to the Lua function.

The solution actually employs variants of the Lua string functions, unicode.utf8.sub and unicode.utf8.len, to allow the argument of \mynewcommand to be any valid string of utf8-encoded characters. (Of course, in order to print the characters in the string, a suitable font has to be loaded.) The argument of \mynewcommand may contain primitives and macros.

enter image description here

% !TEX TS-program = lualatex
\usepackage{luacode} % for "\luaexec" and "\luastringO" macros
% Define a Lua function called "mycommand"
function mycommand ( s )
   local s1,s2,s3
   s1  = unicode.utf8.sub ( s, 1, 1 )
   s2  = unicode.utf8.sub ( s, 2, unicode.utf8.len(s)-2 )
   s3  = unicode.utf8.sub ( s, -2 )
   return ( "\\textsuperscript{" ..s1.. "}" ..s2.. "\\textsubscript{" ..s3.. "}" )
% Create a wrapper macro for the Lua function
abcde $\to$ \mynewcommand{abcde}

öçäßüéà $\to$ \mynewcommand{öçäßüéà}

\mynewcommand{a some text that can contain \textit{\textbf{other commands}} cd}

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.