Atualizar

Atualizar

Editar 2:Na parte inferior, atualizei a pergunta depois de incorporar a resposta daqui:novo comando para índice insere espaços indesejados ao usar {}

Criei uma função para exibir um comando em negrito, opcionalmente fornecendo a seção da página de manual entre parênteses.

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

Agora também quero poder criar uma entrada de índice para esses comandos. Então criei um comando wrapper:

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

No entanto, às vezes quero criar uma entrada de índice usando a mesma formatação, mas não imprimir o comando no texto corrido. Aqui está um 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}

O arquivo output.idxcontém duas linhas, a segunda contendo um espaço adicional. A questão é: por que há espaço adicional e como posso corrigi-lo para que não haja espaço adicional?

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

Editar 1:Adicionei uma captura de tela do resultado desse espaço extra: duas entradas de índice idênticas são impressas.

Entrada de índice duplo

Atualizar

Atualizei meu código de acordo com a solução fornecida, mas agora recebo uma mensagem de erro que não entendo.

\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 afirma:

O compilador está tendo problemas para entender um comando que você usou. Verifique se o comando está escrito corretamente. Se o comando fizer parte de um pacote, certifique-se de ter incluído o pacote em seu preâmbulo usando \usepackage{...}.

O arquivo de log relata:

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

Responder1

Espera-se que o comando \indexobtenha seu argumento lendo e tokenizando coisas do arquivo .tex-input em \@sanitize-category-code-régime. ( \@sanitize-category-code-régime significa: Caractere de espaço, \, $, &, #, ^, e tem código de categoria 12 (outro).) Isso ocorre por vários motivos - por exemplo _,%~

  • evitando expansão indesejada de tokens expansíveis.
  • evitando o acréscimo de um caractere de espaço ao gravar um token de palavra de controle não expandido em um arquivo externo.

Mas com o seu comando \manio comando \indexobtém seu argumento passado de \mani. Quando o argumento de \manicoleta/compõe , os tokens que formam esse argumento não são tokenizados no regime de código de categoria, mas são tokenizados no regime de código de categoria normal.\index\@sanitize

Abaixo de outras coisas, a tokenização sob o regime de código de categoria normal implica que frases como \mansejam tokenizadas como tokens de palavras de controle, não como sequências de caracteres \, m, a, n. Quando os tokens de palavras de controle são gravados sem expansão em um arquivo de texto, por exemplo, algum .idxarquivo pertencente ao processo de criação do índice, um caractere de espaço será anexado. Ou seja, a sequência de caracteres \, m, a, n, ⟨space character⟩será escrita.

Dentro da definição de \manivocê pode aplicar \stringao comando \manpara transformá-lo em uma sequência de tokens de caracteres. (Por meio disso, depende de apenas um caractere de entrada com código de categoria 0 (escape) e o valor do parâmetro inteiro \escapecharsendo igual ao número do ponto de código desse caractere no esquema interno de codificação de caracteres do mecanismo TeX . Normalmente, o caractere de barra invertida \é o único caractere do código de categoria 0 (escape) e geralmente \escapechartem o valor 92, que é o número do ponto de código do caractere de barra invertida no esquema interno de codificação de caracteres do mecanismo 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}

Com o exemplo acima, o arquivo .idx resultante fica assim:

\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}

Com essa abordagem, a única coisa "stringificada" no argumento de \index, que é fornecida por meio da macro \mani, é a frase \man.

As coisas que vêm do \maniprimeiro ou segundo argumento de não são restringidas. Caso os conjuntos de tokens fornecidos por meio desses argumentos também contenham tokens de palavras de controle, você também poderá obter espaços indesejados aqui.

Posso oferecer uma rotina \StringifyNActque se aplica \stringa cada token dentro do seu argumento:

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

rendimentos:

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

em que "stringificação do token" significa o resultado da aplicação \stringao token em questão.
Devido à \romannumeralexpansão, o resultado é obtido acionando duas etapas de expansão (por exemplo, por meio de duas \expandaftercadeias).

Eu sugiro o comando \manipara ler e tokenizar seus argumentos sob o regime de código de categoria normal, mas com o caractere de espaço (e provavelmente também o caractere de tabulação horizontal, endereçável como ^^Ina notação do TeX ^^) sendo do código de categoria 12 (outro ), em seguida, para aplicar \StringifyNActaos argumentos, passando então o resultado deles para o \index-command e - aninhado em \scantokens-para o \man-command:

\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}

Com o exemplo acima, o arquivo .idx resultante fica assim:

\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}

Responder2

O nome do \write-handle usado para o arquivo .idx com memórias difere do nome do \write-handle usado com o LaTeX 2ε-macro \@wrindex.

Portanto, você precisa mapear o nome do "kernel- \write-handle" do índice para o "memoir- \write-handle" do índice:

\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}

O arquivo .idx se parece com isto:

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

Esteja ciente de que - ao contrário da abordagem de estringificação após tokenização apresentada em minha outra resposta - \@sanitizenão lida com o balanceamento de chaves caso os argumentos de \manicontenham o control-symbol-token \{e/ou o control-symbol-token \}.

informação relacionada