4

I'm working on a custom ZSH prompt and I want to repeat a char n times in a string (such as spaces for padding). This string is printed with print -rP (the -r flag ignores echo escape conventions and the -P flag performs prompt expansions).

I have working code using some kind of string substitution, but I don't know how it works. For some reason I have to multiply the number of characters I want to print by two which feels like a hack.

$ n=3
$ c='a'
$ print -rP "${(l:$n::$c:)}" # why doesn't this work?
ca
$ print -rP "${(l:(( $n * 2 ))::$c:)}" # but this does?
aaa

So, 1) why does this work when multiplied by two, and 2) what's the correct syntax to repeat a char within a string?

0

2 Answers 2

5

1) why does this work when multiplied by two,

The expansion "${(l:3::$c:)}" expands to c$c whereas "${(l:3*2::$c:)}" expands to $c$c$c. If the option PROMPT_SUBST is set and this string used as part of a prompt string, it is evaluated for parameter expansion, command substitution and arithmetic expansion. So if c=a, then c$c becomes ca and $c$c$c becomes aaa.

Test with XTRACE set:

$ n=3 c=a zsh -o PROMPT_SUBST -xc 'print -rP -- "${(l:n::$c:)}"'
+zsh:1> print -rP -- 'c$c'
ca
$ n=3 c=a zsh -o PROMPT_SUBST -xc 'print -rP -- "${(l:n*2::$c:)}"'
+zsh:1> print -rP -- '$c$c$c'
aaa

and 2) what's the correct syntax to repeat a char within a string?

The l parameter expansion flag can be used in the same way you are already using it. However, the p flag should be used to allow $c as the string argument to be taken as the value of the variable c prior to padding (thanks @StéphaneChazelas for pointing this out).

$ n=3 c=a zsh -xc 'print -r -- "${(pl:n::$c:)}"'
+zsh:1> print -r -- aaa
aaa

Note that this is the only form of parameter expansion accepted by this construct, as per man zshexpn (in the section about Parameter Expansion Flags):

p   Recognize the same escape sequences as the print builtin in string arguments to any of the flags described below that follow this argument.

Alternatively, with this option string arguments may be in the form $var in which case the value of the variable is substituted. Note this form is strict; the string argument does not undergo general parameter expansion.

2
  • 2
    To pad using the contents of a variable as the padding string, you need the p parameter expansion flag: print -r -- ${(pl[3][$c])} Oct 29, 2021 at 7:40
  • If $c is not a single character this doesn't exactly work how I imagined. Instead I just tweaked the example to multiple n times the length of c to get what I want: n=3 c="applesauce "; print -r -- "${(pl:${#${c}}*n::$c:)}"
    – Chris
    Nov 10, 2021 at 20:55
1

Using your original notation, you can achieve what you wanted, using p at the beginning of the parameter flags

 print "${(pl:$n::$c:)}"

For more info and some other useful examples, see the section 5.4.6: Yet more parameter flags in Chapter 5: Substitutions of the zsh guide. It mentions uppercase P, but not p:


Here are a few other parameter flags; I'm repeating some of these. A very useful one is t to tell you the type of a parameter. This came up in chapter 3 as well. It's most common use is to test the basic type of the parameter before trying to use it:

  if [[ ${(t)myparam} != *assoc* ]]; then
    # $myparam is not an associative array.  Do something about it.
  fi

Another very useful type is for left or right padding of a string, to a specified length, and optionally with a specified fill string to use instead of space; you can even specify a one-off string to go right next to the string in question.

  foo='abcdefghij'
  for (( i = 1; i <= 10; i++ )); do
   goo=${foo[1,$i]}
   print ${(l:10::X::Y:)goo} ${(r:10::X::Y:)goo}
  done

prints out the rather pretty:

  XXXXXXXXYa aYXXXXXXXX
  XXXXXXXYab abYXXXXXXX
  XXXXXXYabc abcYXXXXXX
  XXXXXYabcd abcdYXXXXX
  XXXXYabcde abcdeYXXXX
  XXXYabcdef abcdefYXXX
  XXYabcdefg abcdefgYXX
  XYabcdefgh abcdefghYX
  Yabcdefghi abcdefghiY
  abcdefghij abcdefghij

Note that those colons (which can be other characters, as I explained for the (s) and (j) flags) always occur in pairs before and after the argument, so that with three arguments, the colons in between are doubled. You can miss out the :Y: part and the :X: part and see what happens. The fill strings don't need to be single characters; if they don't fit an exact number of times into the filler space, the last repetition will be truncated on the end furthest from the parameter argument being inserted.

Two parameters tell the shell that you want something special done with the value of the parameter substitution. The (P) flag forces the value to be treated as a parameter name, so that you get the effect of a double substitution:

  % final=string
  % intermediate=final
  % print ${(P)intermediate}
  string

This is a bit as if $intermediate were what in ksh is called a nameref, a parameter that is marked as a reference to another parameter. Zsh may eventually have those, too; there are places where they are a good deal more convenient than the (P) flag.

A more powerful flag is (e), which forces the value to be rescanned for all forms of single-word substitution. For example,

  % foo='$(print $ZSH_VERSION)'
  % print ${(e)foo}
  4.0.2

made the value of $foo be re-examined, at which point the command substitution was found and executed.

The remaining flags are a few simple special formatting tricks: order array elements in normal lexical (character) order with (o), order in reverse order with (O), do the same case-independently with (oi) or (Oi) respectively, expand prompt %-escapes with (%) (easy to remember), expand backslash escapes as print does with p, force all characters to uppercase with (U) or lowercase with (L), capitalise the first character of the string or each array element with (C), show up special characters as escape sequences with (V). That should be enough to be getting on with.

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.