zsh mystery variable expansion - zsh

I found this in prezto source code:
# Set the command name, or in the case of sudo or ssh, the next command.
local cmd="${${2[(wr)^(*=*|sudo|ssh|-*)]}:t}"
I've been reading zsh doc a lot but getting nowhere close to what this is about. In experimentation on the shell itself it seems to indicate the [] is some arithmetic thing, which makes sense, but I don't see the part that explains how (w) is supposed to work. It seems to be some magical operator that applies to the math expression...
slu#ubuntu-sluvm ~/.zprezto ❯❯❯ VAR="one two three four"
slu#ubuntu-sluvm ~/.zprezto ❯❯❯ echo ${VAR[2]}
n
slu#ubuntu-sluvm ~/.zprezto ❯❯❯ echo ${VAR[(w)2]}
two
slu#ubuntu-sluvm ~/.zprezto ❯❯❯ echo ${VAR[(w)]}
zsh: bad math expression: empty string
slu#ubuntu-sluvm ~/.zprezto ❯❯❯

It looks pretty messy at first glance, but once you break it into its parts it's quite simple. This is an example of parameter expansion and extended globbing support in ZSH. If you look higher up in the function from which this code sample is, you'll see they set:
emulate -L zsh
setopt EXTENDED_GLOB
So now let's break apart the line you have there:
${
${
2[ # Expand the 2nd argument
(wr) # Match a word
^(*=*|=|sudo|ssh|-*) # Do not match *=*, =, sudo, ssh, or -*
]
}
:t} # If it is a path, return only the filename
You can test this by creating a sample script like this:
#!/bin/zsh
emulate -L zsh
setopt EXTENDED_GLOB
echo "${$1[(wr)^(*=*|sudo|ssh|-*)]}:t}" # changed 2 to 1, otherwise identical
Here's what it outputs:
$ ./test.sh '/bin/zsh'
zsh
$ ./test.sh 'sudo test'
test
$ ./test.sh 'sudo --flag test'
test
$ ./test.sh 'ssh -o=value test'
test
$ ./test.sh 'test'
test
For more information, see the documentation on expansion and csh-style modifiers.

Related

zsh ${var##$pat} parameter expansion with pattern var containing globs

It seems zsh doesn't honor globs inside variable patterns, in ${var##$pat} parameter expansions:
$ zsh -c 'pat=/*; var=/etc/; echo "$var $pat"; echo "${var##$pat}"'
/etc/ /*
/etc/
# sh result: empty
However, if $pat does not contain *, zsh and sh behave similarly:
$ zsh -c 'pat=/; var=/etc/; echo "$var $pat"; echo "${var##$pat}"'
/etc/ /
etc/
# sh result: same
zsh --emulate sh gives, of course, sh-compatible results. But if I want to stay in zsh emulation, is there any setopt option that changes this behavior? I've looked (briefly) in the docs and I can't really find the reason for this difference.
In zsh, variable contents will only be treated as a pattern if you
ask for that, with a ${~spec} expansion or the (very broad and therefore slightly dangerous) GLOB_SUBST option:
pat=/*t
var=/etc/
print "${var##$pat}"
#=> /etc/
print "${var##$~pat}"
#=> c/
setopt glob_subst
print "${var##$pat}"
#=> c/
This is described in the zshexpn man page, in the section for string substitution expansion ${name/pattern/repl}.

adding a function to .zshrc changes the command

I added the following function to my .zshrc
function jptt(){
# Forwards port $1 into port $2 and listens to it
ssh -N -f -L localhost:$2:localhost:$1 remoteuser#remotehost
}
then I am running jptt 1 2
and get the following error:
Bad local forwarding specification localhost:2ocalhost:1
It is strange that I lose :l after the 2
the function is working when as I tried to replace the command with a simple line and it worked. I also run the ssh command separately and it works well.
The expression $x:l applies the lower-casing modifier to your x variable. The following example illustrates this:
pax> x=ABC
pax> echo $x:lnnn
abcnnn
pax> echo ${x}:lnnn
ABC:lnnn
The first section gives you the lower-case variant, and therefore the modifier is not considered part of your output string. The second section shows how you can prevent this variable expansion by using braces to ensure the :l is not treated as a modifier. In your specific case, that would be done with the line:
ssh -N -f -L localhost:${2}:localhost:${1} remoteuser#remotehost
It's actually a good idea to get into the habit of bracing parameter names as much as possible since there are other cases where this might adversely affect you.
Use ${1} and ${2} . Zsh supports : csh string modificators and :l has special meaning (to lowercase variable in front of it) and that's why it is consumed from $1:localhost.

zsh: access last command line argument given to a script

I want to get the last element of $*. The best I've found so far is:
last=`eval "echo \\\$$#"`
But that seems overly opaque.
In zsh, you can either use the P parameter expansion flag or treat # as an array containing the positional parameters:
last=${(P)#}
last=${#[$#]}
A way that works in all Bourne-style shells including zsh is
eval last=\$$#
(You were on the right track, but running echo just to get its output is pointless.)
last=${#[-1]}
should do the trick. More generally,
${#[n]}
will yield the *n*th parameter, while
${#[-n]}
will yield the *n*th to last parameter.
The colon parameter expansion is not in POSIX, but this works in at least zsh, bash, and ksh:
${#:$#}
When there are no arguments, ${#:$#} is treated as $0 in zsh and ksh but as empty in bash:
$ zsh -c 'echo ${#:$#}'
zsh
$ ksh -c 'echo ${#:$#}'
ksh
$ bash -c 'echo ${#:$#}'
$

In UNIX shell scripting: What does $! expand to?

What is the meaning for $! in shell or shell scripting? I am trying to understand a script which has the something like the following.
local#usr> a=1
local#usr> echo $a
1
local#usr> echo $!a
a
It is printing the variable back. Is it all for that? What are the other $x options we have? Few I know are $$, $*, $?. If anyone can point me to a good source, it will be helpful. BTW, This is in Sun OS 5.8, KSH.
The various $… variables are described in Bash manual. According to the manual $! expands to the PID of the last process launched in background. See:
$ echo "Foo"
Foo
$ echo $!
$ true&
[1] 67064
$ echo $!
67064
[1]+ Done true
In ksh it seems to do the same.
From the ksh man page on my system:
${!vname}
Expands to the name of the variable referred to by vname. This
will be vname except when vname is a name reference.
For the shell you are asking, ksh, use the the ksh manual, and read this:
Parameter Substitution
A parameter is an identifier, one or more digits, or any of
the characters *, #, #, ?, -, $, and !.
It is clear that those are the accepted options $*, $#, $#, $?, $-, $$, and $!.
More could be included in the future.
For the parameter $!, from the manual:
"!" The process number of the last background command invoked.
if you start a background process, like sleep 60 &, then there will be a process number for such process, and the parameter $! will print its number.
$ sleep 60 &
[1] 12329
$ echo "$!"
12329
If there is no background process in execution (as when the shell starts), the exansion is empty. It has a null value.
$ ksh -c 'echo $!'
If there is a background process, it will expand to the PID of such process:
$ ksh -c 'sleep 30 & echo $!'
42586
That is why echo $!a expanded to a. It is because there is no PID to report:
$ ksh -c 'echo $!a'
a
Other shells may have a different (usually pretty similar) list of expansions (a parameter with only one $ and one next character).
For example, bash recognize this *##?-$!0_ as "Special parameters". Search the Bash manual for the heading "3.4.2 Special Parameters".
Special Parameters
The shell treats several parameters specially.
It gives the Process id of last backgroundjob or background function
Please go through this link below
http://www.well.ox.ac.uk/~johnb/comp/unix/ksh.html#specvar
! is a reference operator in unix, though it is not called with that name.
It always refers to a mother process. Try typing :! in vi, it takes you to command prompt and you can execute commands as usual until exit command.
! in SQLPLUS also executes the command from the command prompt. try this in sqlplus
SQL> !ls --- this gives the list of files inthe current dir.
$! - obviously gives the process id of the current/latest process.

Unable to get a system variable work for manuals

I have the following system variable in .zshrc
manuals='/usr/share/man/man<1-9>'
I run unsuccessfully
zgrep -c compinit $manuals/zsh*
I get
zsh: no matches found: /usr/share/man/man<1-9>/zsh*
The command should be the same as the following command which works
zgrep -c compinit /usr/share/man/man<1-9>/zsh*
How can you run the above command with a system variable in Zsh?
Try:
$> manuals=/usr/share/man/man<0-9>
$> zgrep -c compinit ${~manuals}/zsh*
The '~' tells zsh to perform expansion of the <0-9> when using the variable. The zsh reference card tells you how to do this and more.
From my investigations, it looks like zsh performs <> substitution before $ substitution. That means when you use the $ variant, it first tries <> substitution (nothing there) then $ substitution (which works), and you're left with the string containing the <> characters.
When you don't use $manuals, it first tries <> substitution and it works. It's a matter of order. The final version below shows how to defer expansion so they happen at the same time:
These can be seen here:
> manuals='/usr/share/man/man<1-9>'
> echo $manuals
/usr/share/man/man<1-9>
> echo /usr/share/man/man<1-9>
/usr/share/man/man1 /usr/share/man/man2 /usr/share/man/man3
/usr/share/man/man4 /usr/share/man/man5 /usr/share/man/man6
/usr/share/man/man7 /usr/share/man/man8
> echo $~manuals
/usr/share/man/man1 /usr/share/man/man2 /usr/share/man/man3
/usr/share/man/man4 /usr/share/man/man5 /usr/share/man/man6
/usr/share/man/man7 /usr/share/man/man8

Resources