Handling _ (underscores) in a macro's comma-separated list of arguments

You can use \detokenize; but if you want a printed underscore, you need to load fontenc with the T1 option.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{etoolbox}
\usepackage{hyperref}
\usepackage{lipsum}

\newcommand{\codecitep}[1]{% cf. https://tex.stackexchange.com/a/87423/64454
  [%
  \def\nextitem{\def\nextitem{, }}% Separator
  % How to process each item
  \renewcommand*{\do}[1]{%
    \nextitem\hyperref[code:##1]{\detokenize{##1}}%
  }%
  \docsvlist{#1}% Process list
  ]%
}   
\begin{document}

\section{Body}
A sentence with one code-citation only \codecitep{key1}.
Another sentence with two code-citations and followed by dummy text \codecitep{key1, key2}.
A sentence with one code-citation only \codecitep{a_123}.
Another sentence with two code-citations and followed by dummy text \codecitep{a_123, bb_456}.

\lipsum[1-2]

\section{Appendix}
\lipsum[3]

\subsection{key1}
\label{code:key1}
\label{code:a_123}
\lipsum[4]

\subsection{key2}
\label{code:key2}
\label{code:bb_456}
\lipsum[5]
\end{document}

enter image description here

Here's an expl3 version, which doesn't need fontenc.

\documentclass{article}
\usepackage{xparse}
\usepackage{hyperref}
\usepackage{lipsum}

\ExplSyntaxOn

% define a token list containing an underscore
\tl_const:Nx \c_ebo_codecite_us_tl { \char_generate:nn { `_ } { 8 } }

% the main macro
\NewDocumentCommand{\codecitep}{m}
 {
  \ebo_codecite:n { #1 }
 }

% variables and variants of kernel functions
\tl_new:N \l__ebo_codecite_key_print_tl
\seq_new:N \l__ebo_codecite_refs_seq

\cs_generate_variant:Nn \tl_replace_all:Nnn { NV }

% functions

\cs_new_protected:Nn \ebo_codecite:n
 {
  [
   % clear the sequence
   \seq_clear:N \l__ebo_codecite_refs_seq
   % loop through the input
   \clist_map_inline:nn { #1 }
    {
     % for the "print part", change _ into \_
     \tl_set:Nn \l__ebo_codecite_key_print_tl { ##1 }
     \tl_replace_all:NVn \l__ebo_codecite_key_print_tl \c_ebo_codecite_us_tl { \_ }
     % add to the sequence
     \__ebo_codecitep_add:nV { ##1 } \l__ebo_codecite_key_print_tl
    }
   % use the sequence, items separated by "comma space"
   \seq_use:Nn \l__ebo_codecite_refs_seq { ,~ }
  ]
 }

% an auxiliary function, for expanding the second argument
\cs_new_protected:Nn \__ebo_codecitep_add:nn
 {
  \seq_put_right:Nn \l__ebo_codecite_refs_seq { \hyperref[code:#1]{#2} }
 }
\cs_generate_variant:Nn \__ebo_codecitep_add:nn { nV }

\ExplSyntaxOff

\begin{document}

\section{Body}
A sentence with one code-citation only \codecitep{key1}.
Another sentence with two code-citations and followed by dummy text \codecitep{key1, key2}.
A sentence with one code-citation only \codecitep{a_123}.
Another sentence with two code-citations and followed by dummy text \codecitep{a_123, bb_456}.

\lipsum[1-2]

\section{Appendix}
\lipsum[3]

\subsection{key1}
\label{code:key1}
\label{code:a_123}
\lipsum[4]

\subsection{key2}
\label{code:key2}
\label{code:bb_456}
\lipsum[5]
\end{document}

enter image description here


The simplest way to do this is to load the url package and define a url command to format the refnames:

\usepackage{url}
\DeclareUrlCommand{\coderefname}{\urlstyle{rm}}

\newcommand{\codecitep}[1]{% cf. https://tex.stackexchange.com/a/87423/64454
    [%
    \def\nextitem{\def\nextitem{, }}% Separator
    \renewcommand*{\do}[1]{\nextitem{\hyperref[code:##1]{\coderefname{##1}}}}% How to process each item
    \docsvlist{#1}% Process list
    ]%
}