Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

"cat -v" in zsh (and a minor bug)

A friend recently asked me how to get the equivalent of

	visible=$(echo "$string" | cat -v)

without requiring the extra processes.

Entirely coincidentally, a patch  for 3.1.6 was just posted to make ${(V)var}
return the value of $var in "visible" format; so after the next release, the
following will be moot.  However, there's a cute trick below that's of some
interest; read on past the end of the function (the bug report is there too).

In the meanwhile, or in 3.0.x, the following makes $visible from $string.

# function string-to-visible {
# emulate -LR zsh 2>/dev/null || emulate -R zsh && setopt localoptions
# typeset string="$*"
typeset a q z
typeset -i i=0
typeset -i8 oct
z=('' '')			# Prepare for cute trick
while (( i <= $#string ))
  if (( #a < 0 || #a > 127 )); then
    (( oct = #a & 127 ))
    (( oct = #a ))
  if (( oct < 32 )); then
    (( oct = oct + #\A - 1 ))
    q="$q^"			# Replace ^ with C- for emacs-style
  if (( $#q )); then
    a=${(e)q::='${(pj:'"$q\\${oct#*#}"':)z}'}		# Cute trick!
  (( ++i ))
# }

About that trick ... a few weeks ago Sven posted a patch for ${(%)var} to
cause prompt expansion to happen on $var.  Someone remarked that it would
be nice to have "print" command escapes interpreted similarly.  That's what
the cute trick, above, accomplishes:  The (p) flag causes print escapes to
be recognized in the rest of the flags.  The $z array provides two empty
strings that can be joined.  So the trick is to stick the value, in which
you want print escapes expanded, between the colons in ${(pj::)z}.  But
parameter expansion doesn't happen inside the flags of another expansion,
so we have to construct the whole ${(pj:...:)z} string that we want as a
new value, and then evaluate that with ${(e)...}.

The rest of the function is just conversion of each ascii character to the
appropriate octal value for "print" to turn into a printable character.

The bug is demonstrated by:

a='å'				# That's meta-a, or 228 decimal
(( #a == #\å )) || echo oops

The problem is that #a is unsigned but #\a is signed, so for values above
127 decimal the #\a form returns a negative number.

Bart Schaefer                                 Brass Lantern Enterprises
http://www.well.com/user/barts              http://www.brasslantern.com

Messages sorted by: Reverse Date, Date, Thread, Author