4

I am using the listings package to show some sample bash script lines; however, the otherkeywords argument is giving me trouble. As the listings documentation states:

If one keyword is a subsequence of another (like -- and -->), you must specify the shorter first.

But this does not seem to be borne out in practice, at least not with curly brace characters. I'll admit I'm hoping this is simply some escape syntax mistake.

\documentclass[preview,varwidth,border=5pt]{standalone}

\usepackage[T1]{fontenc}
\usepackage{
  beramono,
  listings,
  textcomp
}

\lstset{breaklines=true,
frame=tb,
language=bash,
breakatwhitespace=true,
alsoletter={*()"'0123456789.},
alsoother={\{\=\}},
basicstyle={\small\ttfamily},
commentstyle={\itshape},
keywordstyle={\bfseries},
literate={{=}{{{=}}}1},
prebreak={\textbackslash},
sensitive=true,
stepnumber=1,
tabsize=4,
upquote=true,
morekeywords={echo, function},
otherkeywords={},
otherkeywords={
  -, \{, $\{, \}
}}
%% Removing the left curly-brace from the list of otherkeywords make variable names look right but not function clauses.
%% (The hyphen is needed only because the otherkeywords list cannot start with an escaped curly brace.)

\begin{document}

\begin{lstlisting}
function test() { VAR='$12'; echo ${VAR}; }
\end{lstlisting}

Should look like:

\texttt{{\bfseries{}function} test() {\bfseries{}\{} VAR='\$12'; {\bfseries{}\$\{}VAR{\bfseries{}\}}; {\bfseries{}\}}}

but all of the following are wrong:

\texttt{{\bfseries{}function} test() {\bfseries{}\{} VAR='\$12'; \${\bfseries{}\{}VAR{\bfseries{}\}}; {\bfseries{}\}}}

\texttt{{\bfseries{}function} test() \{ VAR='\$12'; {\bfseries{}\$\{}VAR{\bfseries{}\}}; {\bfseries{}\}}}

\texttt{{\bfseries{}function} test() {\bfseries{}\{} VAR='{\bfseries{}\$}12'; {\bfseries{}\$\{}VAR{\bfseries{}\}}; {\bfseries{}\}}}

\end{document}

Result: (notice which characters are in bold) curly braces in bash script in listing

In short, placing the \{ keyword first does not allow the $\{ keyword to also be recognized. Moving \{ in the list order makes no difference, whereas removing it allows $\{ to be recognized, but not a single { character. I've also tried escaping the $ character to no effect.

Please note that this does not seem to be exactly a duplicate of e.g. R listings operator partially in bold because placing the short version first, as the accepted answer/workaround there points out, does not solve my case.

  • No sooner did I post this than I read (tex.stackexchange.com/questions/209632/…) which gave me of course the idea to just add literate={\$\{}{{{{\bfseries{}\$\{}}}}2, and remove $\{ from otherkeywords entirely. Good enough workaround for me. – courtlandj May 18 '16 at 20:35
  • So post your own answer as an answer instead of a comment. – A Feldman May 19 '16 at 4:59
  • Good point @AFeldman. Done. – courtlandj May 19 '16 at 21:16
4

Reading Bash lstlistings treats “$#” as a comment gave me the idea to just add a literate to solve this case. In my listings settings, I added literate={\$\{}{{{{\bfseries{}\$\{}}}}2, and removed $\{ from the otherkeywords list entirely. In point of fact, unlike my MWE, since I am using LyX, I solved it by adding it to an \AtBeginDocument{} in my LaTeX Preamble, e.g.:

\documentclass[varwidth,border=5pt]{standalone}

\usepackage[T1]{fontenc}
\usepackage{
  color,
  beramono,
  listings,
  textcomp
}

\definecolor{lightgray}{RGB}{245,245,245}
\definecolor{darkgray}{RGB}{128,128,128}

\lstset{
  abovecaptionskip={0cm},
  backgroundcolor={\color{lightgray}},
  basicstyle={\small\ttfamily},
  breakatwhitespace=true,
  breaklines=true,
  captionpos=b,
  frame=tb,
  resetmargins=true,
  sensitive=true,
  stepnumber=1,
  tabsize=4,
  upquote=true
}

\AtBeginDocument{\lstdefinelanguage{bash}[]{sh}%
  {morekeywords={alias,bg,bind,builtin,caller,command,compgen,compopt,%
      complete,coproc,curl,declare,disown,dirs,enable,fc,fg,help,%
      history,jobs,let,local,logout,mapfile,printf,pushd,popd,%
      readarray,select,set,suspend,shopt,source,times,type,typeset,%
      ulimit,unalias,wait},%
   otherkeywords={ [, ], [[, ]], \{, \} }%
  }%

\lstdefinelanguage{sh}%
  {morekeywords={awk,break,case,cat,cd,continue,do,done,echo,elif,else,%
      env,esac,eval,exec,exit,export,expr,false,fi,for,function,getopts,%
      hash,history,if,in,kill,login,newgrp,nice,nohup,ps,pwd,read,%
      readonly,return,set,sed,shift,test,then,times,trap,true,type,%
      ulimit,umask,unset,until,wait,while},%
   morecomment=[l]\#,%
   morestring=[d]",%
   alsoletter={*"'0123456789.},%
   alsoother={\{\=\}},%
   literate={{=}{{{=}}}1},%
   literate={\$\{}{{{{\bfseries{}\$\{}}}}2,%
   otherkeywords={ [, ], \{, \} }%
  }[keywords,comments,strings]%
}

\begin{document}

\begin{lstlisting}[language=bash]
function test() { VAR='$12'; echo ${VAR}; }
for i in "one" "two" "three" "four" "five"; do
  printf "%7s\n" "${i}"
done
\end{lstlisting}

\end{document}

So, perhaps redefining my bash and sh listing language definitions is overkill, but this specific way, I get to keep it in the document rather than editing my lstlangX.sty files directly and it won't collide with other languages you might also list in the same document, like Python. It's less than pretty, but you don't get the so-called ERT in LyX, for what it's worth.

Result: New sort-of LyX-friendly result

(This is a more specific answer than the one in my original question; not so much a mere correction of the MWE, but this was my specific case, so the above is closer to my specific solution.)

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.