Обновлять

Обновлять

Редактировать 2:Внизу я обновил вопрос, включив ответ отсюда:newcommand для индекса вставляет нежелательные пробелы при использовании {}

Я создал функцию для отображения команды жирным шрифтом с возможностью указания раздела страницы руководства в скобках.

\usepackage{xparse}
\newcommand*{\man}[2][]{%
   \textbf{#2}\IfNoValueF{#1}{(#1)}%
}

Теперь я также хочу иметь возможность создать индексную запись для этих команд. Поэтому я создал команду-обертку:

\newcommand*{\mani}[2][]{%
   \man[#1]{#2}%
   \index{#2@\man[#1]{#2}}%
}

Однако иногда мне хочется создать запись индекса, используя то же форматирование, но не печатать команду в текущем тексте. Вот MWE:

\documentclass{memoir}
\usepackage{xparse}
\newcommand*{\man}[2][]{%
   \textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\newcommand*{\mani}[2][]{%
   \man[#1]{#2}%
   \index{#2@\man[#1]{#2}}%
}
\makeindex

\begin{document}
Only an index entry: \index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world

Command and index entry: \mani[1]{ssh-keygen}

\printindex
\end{document}

Файл output.idxсодержит две строки, вторая содержит дополнительный пробел. Таким образом, вопрос в том, почему там есть дополнительный пробел и как это исправить, чтобы не было дополнительного пробела?

\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man [1]{ssh-keygen}}{1}

Редактировать 1:Я добавил снимок экрана с результатом этого дополнительного пространства: печатаются две идентичные записи индекса.

Двойная запись индекса

Обновлять

Я обновил свой код в соответствии с предоставленным решением, но теперь получаю сообщение об ошибке, которое я не понимаю.

\documentclass{memoir}
\usepackage{xparse}
\makeindex
\NewDocumentCommand\man{om}{%
   \textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\makeatletter
\newcommand*{\mani}{%
   \@bsphack
   \begingroup
   \@sanitize
   \@mani
}
\newcommand*{\@mani}[2][]{%
   \man[#1]{#2}%
   \@wrindex{#2@\string\man[#1]{#2}}%
}
\makeatletter


\begin{document}
Two separate commands: \man[1]{ssh-keygen}\index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world

One command: \mani[1]{ssh-keygen}

\printindex
\end{document}

OverLeaf утверждает:

Компилятор не может понять команду, которую вы использовали. Проверьте правильность написания команды. Если команда является частью пакета, убедитесь, что вы включили пакет в преамбулу с помощью \usepackage{...}.

В файле журнала сообщается:

! Missing number, treated as zero.
<to be read again> 
                   {
l.25     One command: \mani[1]{ssh-keygen}
                                          
A number should have been here; I inserted `0'.
(If you can't figure out why I needed to see a number,
look up `weird error' in the index to The TeXbook.)

решение1

Команда \indexдолжна получить свой аргумент путем чтения и токенизации элементов из файла .tex-input в разделе \@sanitize-category-code-régime. ( \@sanitize-category-code-régime означает: символ пробела, \, $, &, #, ^, и имеет код категории 12 (другое). _) Это происходит по нескольким причинам, например,%~

  • предотвращение нежелательного расширения расширяемых токенов.
  • избегание добавления символа пробела при неразвернутой записи токена управляющего слова во внешний файл.

Но с вашей командой \maniкоманда \indexполучает свой аргумент, переданный из \mani. Когда аргумент \manigathers/composes \index, токены, формирующие этот аргумент, не токенизируются в соответствии с \@sanitize-category-code-régime, а токенизируются в соответствии с обычным category-code-régime.

Под другими вещами токенизация в обычном режиме category-code подразумевает, что фразы типа \manтокенизируются как токены-управляющих-слов, а не как последовательности-символов \, m, a, n. Когда токены-управляющих-слов записываются неразвернутыми в текстовый-файл, например, некоторый .idx-файл, принадлежащий процессу создания индекса, будет добавлен символ-пробел. Т.е. будет записана последовательность-символов \, m, a, n, .⟨space character⟩

В определении \maniвы можете применить \stringкоманду \man, чтобы превратить ее в последовательность символов-токенов. (Здесь полагается только один входной символ, имеющий код категории 0 (escape), и значение целочисленного параметра, \escapecharравное номеру кодовой точки этого символа во внутренней схеме кодировки символов движка TeX. Обычно символ обратной косой черты \является единственным символом с кодом категории 0 (escape) и обычно \escapecharимеет значение 92, которое является номером кодовой точки символа обратной косой черты во внутренней схеме кодировки символов движка TeX.)

\documentclass{memoir}
\usepackage{xparse}
\NewDocumentCommand{\man}{om}{%
   \textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\NewDocumentCommand{\mani}{om}{%
   \IfNoValueTF{#1}{%
      \man{#2}%
      \index{#2@\string\man{#2}}%
    }{%
      \man[#1]{#2}%
      \index{#2@\string\man[#1]{#2}}%
    }%
}%
\makeindex

\begin{document}

Only an index entry: \index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world

Command and index entry: \mani[1]{ssh-keygen}

Only an index entry: \index{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}
Hello world

Command and index entry: \mani{ssh-keygen-no-optional-argument}

\printindex
\end{document}

В приведенном выше примере результирующий .idx-файл выглядит следующим образом:

\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}

При таком подходе единственное, что «строковывается» в аргументе \index, который предоставляется через макрос \mani, — это фраза \man.

Вещи, которые приходят из \maniпервого или второго аргумента, не преобразуются в строку. В случае, если наборы токенов, предоставленные через эти аргументы, также содержат токены-управления-словами, вы также можете получить здесь нежелательные пробелы.

Я могу предложить процедуру \StringifyNAct, которая применяется \stringк каждому токену в его аргументе:

\StringifyNAct{⟨action⟩}{⟨token 1⟩⟨token 2⟩...⟨token n⟩}

урожайность:

⟨action⟩{⟨stringification of token 1⟩}%
⟨action⟩{⟨stringification of token 2⟩}%
...
⟨action⟩{⟨stringification of token n⟩}%

где «стрингификация токена» означает результат применения \stringк рассматриваемому токену.
Благодаря \romannumeral-расширению результат доставляется путем запуска двух шагов расширения (например, через две \expandafter-цепочки).

Я предлагаю команде \maniсчитывать и токенизировать свои аргументы в обычном режиме кодов категорий, но с символом пробела (и, вероятно, также с символом горизонтальной табуляции, адресуемым как ^^Iс помощью -нотации TeX ^^) с кодом категории 12 (другие), затем применять ее \StringifyNActк аргументам, затем передавать результат в -команду \indexи — вложенным в \scantokens\manв -команду:

\documentclass{memoir}

\makeatletter
%%========================Code for \StringifyNAct==============================
%%
%% Copyright (C) 2019, 2020 by Ulrich Diez ([email protected])
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public Licence (LPPL), either
%% version 1.3 of this license or (at your option) any later
%% version. (The latest version of this license is in:
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included
%% documentation nor for any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo,
%%    \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%%    \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%%    \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  { #1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------    
%% In case an argument's first token is an opening brace, stringify that and
%% add another opening brace before that and remove everything behind the 
%% matching closing brace:
%% \UD@StringifyOpeningBrace{{Foo}bar} yields {{Foo}  whereby the second
%% opening brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyOpeningBrace[1]{%
  \romannumeral0%
  \expandafter\UD@ExtractFirstArgLoop\expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\expandafter
    \expandafter            {%
    \expandafter\UD@firstoftwo
    \expandafter{%
    \expandafter}%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\string
    \expandafter}%
    \string#1%
  UD@SelDOm}%
}%
%%-----------------------------------------------------------------------------    
%% In case an argument's first token is an opening brace, remove everything till 
%% finding the corresponding closing brace. Then stringify that closing brace:
%% \UD@StringifyClosingBrace{{Foo}bar} yields: {}bar} whereby the first closing
%% brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyClosingBrace[1]{%
   \romannumeral0\expandafter\expandafter\expandafter
                 \UD@StringifyClosingBraceloop
                 \UD@ExtractFirstArg{#1}{#1}%
}%
\newcommand\UD@CheckWhetherStringifiedOpenBraceIsSpace[1]{%
%% This can happen when character 32 (space) has catcode 1...
  \expandafter\UD@CheckWhetherLeadingSpace\expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\UD@secondoftwo
    \expandafter{%
    \expandafter}%
    \expandafter{%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\UD@firstoftwo
    \expandafter{%
    \expandafter}%
    \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
    \expandafter\string
    \expandafter}%
    \string#1%
  }%
}%
\newcommand\UD@TerminateStringifyClosingBraceloop[2]{%
  \UD@Exchange{ }{\expandafter\expandafter\expandafter}%
  \expandafter\expandafter
  \expandafter{%
  \expandafter\string      
  \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
  \expandafter#1%
  \string#2%
  }%
}%
\newcommand\UD@StringifyClosingBraceloopRemoveElement[4]{%
  \expandafter\UD@PassFirstToSecond\expandafter{\expandafter
  {\romannumeral0\expandafter\UD@secondoftwo\string}{}%
    \UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{%
      \UD@Exchange{\UD@removespace}%
    }{%
      \UD@Exchange{\UD@firstoftwo\expandafter{\expandafter}}%
    }{%
      \UD@Exchange{ }{\expandafter\expandafter\expandafter}%
      \expandafter#1%
      \romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
      \expandafter
    }%
    \string#4%
  }{\expandafter\UD@StringifyClosingBraceloop\expandafter{#2#3}}%
}%
\newcommand\UD@StringifyClosingBraceloop[2]{%
  \UD@CheckWhetherNull{#1}{%
    \UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{%
      \UD@TerminateStringifyClosingBraceloop{\UD@removespace}%
    }{%
      \UD@TerminateStringifyClosingBraceloop{\UD@firstoftwo\expandafter{\expandafter}}%
    }%
    {#2}%
  }{%
    \UD@CheckWhetherLeadingSpace{#1}{%
      \UD@StringifyClosingBraceloopRemoveElement
      {\UD@removespace}{\UD@removespace}%
    }{%
      \UD@StringifyClosingBraceloopRemoveElement
      {\UD@firstoftwo\expandafter{\expandafter}}{\UD@firstoftwo{}}%
    }%
    {#1}{#2}%
  }%
}%
%%-----------------------------------------------------------------------------    
%% Apply <action> to the stringification of each token of the argument:
%%
%% \StringifyNAct{<action>}{<token 1><token 2>...<token n>}
%%
%% yields:  <action>{<stringification of token 1>}%
%%          <action>{<stringification of token 2>}%
%%          ...
%%          <action>{<stringification of token n>}%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%% If you leave <action> empty, you can apply a loop on the list formed by
%%   {<stringification of token 1>}%
%%   {<stringification of token 2>}%
%%   ...
%%   {<stringification of token n>}%
%%
%% Below a macro \ConcatenateStringifiedtokens is implemented which loops
%% on that list for concatenating.
%%.............................................................................
\newcommand\StringifyNAct{%
  \romannumeral0\StringifyNActLoop{}%
}%
%%.............................................................................
%% \StringifyNActLoop{{<stringification of token 1>}...{<stringification of token k-1>}}%
%%                   {<action>}%
%%                   {<token k>...<token n>}
%%.............................................................................
\newcommand\StringifyNActLoop[3]{%
  \UD@CheckWhetherNull{#3}{%
    \UD@firstoftwo{ }{}#1%
  }{%
    \UD@CheckWhetherBrace{#3}{%
      \expandafter\expandafter\expandafter\UD@Exchange
      \expandafter\expandafter\expandafter{%
        \UD@StringifyClosingBrace{#3}%
      }{%
        \expandafter\StringifyNActLoop\expandafter{%
          \romannumeral0%
          \expandafter\expandafter\expandafter\UD@Exchange
          \expandafter\expandafter\expandafter{\UD@StringifyOpeningBrace{#3}}{\StringifyNActLoop{#1}{#2}}%
        }{#2}%
      }%
    }{%
      \UD@CheckWhetherLeadingSpace{#3}{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@removespace#3}{%
          \StringifyNActLoop{#1#2{ }}{#2}%
        }%
      }{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{%
          \expandafter\StringifyNActLoop\expandafter{%
             \romannumeral0%
             \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\UD@PassFirstToSecond
             \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter{%
               \expandafter\expandafter\expandafter\string
               \expandafter\UD@Exchange
               \romannumeral0\UD@ExtractFirstArgLoop{#3UD@SelDOm}{}%
             }{ #1#2}%
          }%
          {#2}%
        }%
      }%
    }%
  }%
}%
%% The promised loop for concatenating stringified tokens - apply as:
%%
%%      \romannumeral0%
%%      \expandafter\expandafter\expandafter
%%      \ConcatenateStringifiedtokens
%%      \StringifyNAct{}{<tokens to stringify>}\relax
%%
\newcommand*\ConcatenateStringifiedtokens{%
  \ConcatenateStringifiedtokensloop{ }%
}%
\newcommand\ConcatenateStringifiedtokensloop[2]{%
  \ifx\relax#2\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
  {#1}{%
    \ConcatenateStringifiedtokensloop{#1#2}%
  }%
}%
%%=================== End of code for \StringifyNAct ==========================
\makeatother

\usepackage{xparse}

\makeatletter
\NewDocumentCommand{\man}{om}{%
   \UD@CheckWhetherNull{#2}{}{%
     \toks@{#2}%
     \textbf{\the\toks@}%
   }%
   \IfNoValueF{#1}{(#1)}%
}%
\NewDocumentCommand{\mani}{}{%
  \begingroup
  \catcode`\ =12\relax
  \catcode`\^^I=12\relax
  \maniinner
}%
\NewDocumentCommand{\maniinner}{om}{%
   \endgroup
   \IfNoValueTF{#1}{%
      \expandafter\maniinnerinner\expandafter{%
        \romannumeral0%
        \expandafter\expandafter\expandafter
        \ConcatenateStringifiedtokens
        \StringifyNAct{}{#2}\relax
      }%
    }{%
      \expandafter\UD@PassFirstToSecond\expandafter{%
        \romannumeral0%
        \expandafter\expandafter\expandafter
        \ConcatenateStringifiedtokens
        \StringifyNAct{}{#2}\relax
      }{%
        \expandafter\maniinnerinner\expandafter[\expandafter{%
          \romannumeral0%
          \expandafter\expandafter\expandafter
          \ConcatenateStringifiedtokens
          \StringifyNAct{}{#1}\relax
        }]%
      }%
    }%
}%
\makeatother
\begingroup
\newcommand\maniinnerinner[1]{%
  \endgroup
  \NewDocumentCommand{\maniinnerinner}{om}{%
     \IfNoValueTF{##1}{%
        \scantokens{\man{##2}#1}%
        \index{##2@\string\man{##2}}%
      }{%
        \scantokens{\man[##1]{##2}#1}%
        \index{##2@\string\man[##1]{##2}}%
      }%
  }%
}%
\catcode`\%=12\relax
\maniinnerinner{%}%

\makeindex

\begin{document}

Only an index entry: \index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world

Command and index entry: \mani[1]{ssh-keygen}

Only an index entry: \index{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}
Hello world

Command and index entry: \mani{ssh-keygen-no-optional-argument}

\newcommand\ke{ke}%
\newcommand\one{1}%

Only an index entry: \index{ssh-\ke y\string#gen@\man[\one]{ssh-\ke y\string#gen}}
Hello world

Command and index entry: \mani[\one]{ssh-\ke y\string#gen}


\printindex
\end{document}

В приведенном выше примере результирующий .idx-файл выглядит следующим образом:

\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}
\indexentry{ssh-\ke y\string#gen@\man[\one]{ssh-\ke y\string#gen}}{1}
\indexentry{ssh-\ke y\string#gen@\man[\one]{ssh-\ke y\string#gen}}{1}

решение2

Имя дескриптора, \writeиспользуемого для .idx-файла с мемуарами, отличается от имени дескриптора, \writeиспользуемого с макросом LaTeX 2ε \@wrindex.

Таким образом, вам необходимо сопоставить имя «kernel- \write-handle» для индекса с «memoir- \write-handle» для индекса:

\documentclass{memoir}
\usepackage{xparse}
\makeindex
\NewDocumentCommand\man{om}{%
   \textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\makeatletter
\newcommand*{\mani}{%
   \@bsphack
   \begingroup
   \@sanitize
   \@mani
}
\NewDocumentCommand{\@mani}{om}{%
   \@ifundefined{@indexfile}{%
     \expandafter\let\expandafter\@indexfile\csname\jobname @idxfile\endcsname
   }{}%
   \IfNoValueTF{#1}{%
     \man{#2}%
     \@wrindex{#2@\string\man{#2}}%
   }{%
     \man[#1]{#2}%
     \@wrindex{#2@\string\man[#1]{#2}}%
   }%
}
\makeatletter

\begin{document}
Two separate commands: \man[1]{ssh-keygen}\index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world

One command: \mani[1]{ssh-keygen}

\printindex
\end{document}

Файл .idx выглядит так:

\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}

Имейте в виду, что в отличие от подхода стрингификации после токенизации, представленного в моем другом ответе, \@sanitizeне выполняется балансировка фигурных скобок в случае, если аргументы \maniдолжны содержать control-symbol-token \{и/или control-symbol-token \}.

Связанный контент