Shell – way to make the prompt definition multiline

command linepromptshellzsh

This is my current prompt definition:

PS1=$'%F{063}%1~%f %(1v.%F{099}%1v %f.)%F{063}%%%f '
RPROMPT='$VIMODE %m'

and I'm working on integrating this. Basically I'm starting to find it very unreadable.

Is there any way that I can make it multiline in a way perhaps similar to what Perl can do with regex's (e.g., what /x mode does. Like m{ ... }x. the ... can be multiline in that)?

Something like this:

PS1=$'
   %F{063}%1 # format blue
      ~      # show current directory
   %f
   %(1v.%F{099}%1v %f.) # show git branch if git repo in purple
   %F{063}   # format blue
      %#     # % for user and # for root
   %f '
RPROMPT='$VIMODE %m'

Best Answer

With Zsh 4.3.11, you can use the Z parameter expansion flag to split a string value according to the normal shell parsing rules while discarding comments (C option to Z) and treating newlines as normal whitespace instead of replacing them with semicolons (n option to Z). You can then stitch the results back together (j::) and evaluate a level of quoting (Q) to let you quote whitespace and other problematic characters (like “bare” comment introducer characters):

PS1=${(j::Q)${(Z:Cn:):-$'
   %F{063}%1 # format blue
      ~      # show current directory
   %f" "
   %(1v.%F{099}%1v %f.) # show git branch if git repo in purple
   %F{063}   # format blue
      %#     # % for user and # for root
   %f" "
'}}

Note: This parsing mode seems to know that it should parse the whole %(v…) expression as a single word, so we do not have to protect the space embedded in the conditional value. However, we do need to protect the “top level” spaces (the ones that happen to come after %f) since those will otherwise be taken as a normal word separator. The final unquoting pass will process any quoting mechanism (i.e. \, '', "", $''), so you can pick what you use to protect special characters (e.g. “top level” spaces or comment introducers intended for the final value).


If you are not using 4.3.11, then you can use an array to let you intersperse comments with the string elements. You will probably have to use more quoting than the with the Z parameter expansion flag, but the result may still be tolerable.

ps1_arr=(
   %F{063}%1 # format blue
      \~     # show current directory
   %f' '
   '%(1v.%F{099}%1v %f.)' # show git branch if git repo in purple
   %F{063}   # format blue
      %\#    # % for user and # for root
   %f' '
)
PS1=${(j::)ps1_arr}

Some notes on the quoting:

  • You can avoid quoting the ~ if you say %1~ instead of splitting it (it is %~ with an argument of 1, after all).
  • I quoted the whole %(v…) word, but only the parentheses and the space need protection.
  • You only need to quote the # in %# if you have EXTENDED_GLOB enabled.
  • The spaces that happen to come after %f need some kind of quoting. You can use a backslash, but it might look like a line continuation if you do not have “visible whitespace” in your editor.
Related Question