How to initialize array from array in zsh? - zsh

My script test.zsh:
args=$#
argss=($#)
echo ${#:2}
echo ${args:2}
echo ${argss:2}
The output:
$ ./test.zsh foo bar foobar
bar foobar
o bar foobar
o
It looks like args is being initialized as the string of $# instead of as an array. How do I initialize args as an array? ($#) does not seem to work either

You need to put parentheses around $# to make args an array:
args=($#)
In other shells, you should also put quotes around it (args=("$#")) to avoid word splitting, but this is disabled by default in zsh (see the option SH_WORD_SPLIT).
Note that ${#:2} will give you $1 $2 $3 ..., while ${args:2} will give $2 $3 ..., because zsh prepends $0 to $# when you use that form of parameter subscripting for compatibility with other shells.
The preferred zsh way to subscript arrays is ${arr[start,end]}, where end is inclusive and may be negative.
${args[1,-1]} and ${args[#]} will expand to the same thing.

Related

bjam - cannot assign a literal to a variable?

Well, this must be the most stupid and idiotic behavior I've seen from a programming language.
https://www.bfgroup.xyz/b2/manual/release/index.html says:
Syntactically, a Boost.Jam program consists of two kinds of
elements—keywords (which have a special meaning to Boost.Jam) and
literals. Consider this code:
a = b ;
which assigns the value b to the variable a. Here, = and ; are
keywords, while a and b are literals.
⚠ All syntax elements, even
keywords, must be separated by spaces. For example, omitting the space
character before ; will lead to a syntax error.
If you want to use a literal value that is the same as some keyword,
the value can be quoted:
a = "=" ;
OK, so far so good. So I have this in my Jamroot:
import path : basename ;
actions make_mytest_install
{
echo "make_mytest_install: MY_ROOT_PATH $(MY_ROOT_PATH) PWD $(PWD:E=not_set)" ;
epath = "$(MY_ROOT_PATH)/projects/mytest/bin/gcc-9/release/qt5client" ;
ename = basename ( $(epath) ) ;
echo "epath $(epath) ename $(ename)" ;
}
explicit install-gettext ;
make install-mytest : : #make_mytest_install ;
... and I try this:
bjam install-mytest
...updating 1 target...
Jamfile</home/USER/src/myproject>.make_mytest_install bin/install-mytest
make_mytest_install: MY_ROOT_PATH /home/USER/src/myproject PWD not_set
[ SHELL pstree -s -p 2720269 && echo PID 2720269 PWD /home/USER/src/myproject ]
/bin/sh: 13: epath: not found
/bin/sh: 14: Syntax error: "(" unexpected
.....
...failed Jamfile</home/USER/src/myproject>.make_mytest_install bin/install-mytest...
...failed updating 1 target...
Now - how come that the SIMPLEST assignment to a string, EXACTLY AS in the manual:
epath = "$(MY_ROOT_PATH)/projects/mytest/bin/gcc-9/release/qt5client" ;
... fails, and this variable cannot be found anymore?
What is the logic in this? How the hell is this supposed to work? I would get it if MY_ROOT_PATH was undefined - but the echo before it, shows that it is not? What is this lunacy?
So I cannot believe I'm asking something this trivial, but:
How do you assign a string to a variable in bjam language?
Well, the error gives somewhat of a hint: /bin/sh: -> so apparently inside actions, it is sh that runs - then again, if it was really sh I could have assigned variables, but I can't. So best I could do, was to remove the assignments OUT of actions:
import path : basename ;
epath = "$(MY_ROOT_PATH)/projects/mytest/bin/gcc-9/release/qt5client" ;
# ename = basename ( $(epath) ) ; # nope, causes target install-mytest to not be found :(
# calling a shell for basename works - but adds a damn NEWLINE at end!?!?!?!
ename = [ SHELL "basename $(epath)" ] ;
actions make_mytest_install
{
echo "make_mytest_install: MY_ROOT_PATH $(MY_ROOT_PATH) PWD $(PWD:E=not_set)" ;
echo "epath $(epath) ename $(ename)" ;
}
explicit install-mytest ;
make install-mytest : : #make_mytest_install ;
So, assignment kind of passes, but you still can't get the basename ?!
I still don't understand, who thought this kind of variable management is a good idea ... I don't even understand, how people managed to build stuff with this system

zsh completion complete literal quotes

I am writing my own completions for a program.
I would like to be able to complete quoted words, maintaining the double or single quotes in the completion.
#compdef foo
_foo {
local strings
strings=(\
foo\
bar\
'spam eggs')
_arguments \
{-s,--string}'[Select a string]:STR:(\""${strings[#]}"\")\
&& return 0
}
_foo
what I'd expect:
foo -s <TAB>
"foo" "bar" "spam eggs"
what it get:
\"foo\" \"bar\" \"spam\ eggs\"
I ended up trying different combinations of nested quotes and escapes almost brainlessly but with no luck, as I was not able to find the relevant docs (really, zsh docs are "dense")
Thank you!

HP-UX KSH scripting - passing blank parameters with $#

I have a 'problem' with a script I'm developing in HP-UX KSH. The script contains many functions, and I need to pass the same set of parameters between them. All well and good, but some parameters can be blank. Its easy to pass blank parameters using double double-quotes (""), but what if I want to pass a complete set of parameters from one function into another using ${#}, including blanks? And to make things tricky, there can be a variable number of parameters each time, so the method has to be dynamic.
Example: I've got a function called test1 that takes a number of parameters. Any of them can be blank. I've also created a function called test2 into which all the parameters of test1 are passed:
test1()
{
echo 1-1: ${1}
echo 1-2: ${2}
test2 ${#}
}
test2()
{
echo 2-1: ${1}
echo 2-2: ${2}
}
# test1 "" hello
1-1:
1-2: hello
2-1: hello
2-2:
The trouble is, if ${1} is blank, ${2} from test1 appears as ${1} in test2. So to work around the problem I created this code, which effectively creates a function string with all parameters surrounded with double quotes:
test1()
{
typeset var FUNC="test2"
typeset -i var COUNT=1
echo 1-1: ${1}
echo 1-2: ${2}
while [ ${COUNT} -le ${##} ]; do
typeset var PARAM=$(eval "echo \$${COUNT}")
FUNC="${FUNC} \"${PARAM}\""
((COUNT=COUNT+1))
done
eval "${FUNC}"
}
# test1 "" hello
1-1:
1-2: hello
2-1:
2-2: hello
This works very nicely, thank you. Now to my 'problem'.
Is it actually possible to encapsulate the above code in a function of its own? It seems a catch 22 to me, in that you have to run that code to pass the blank parameters. I have to repeat this code snippet many times in my script because I can't find another way. Is there one?
Any help or guidance will be gratefully received.
Here is how I would write your functions:
show_params() {
typeset funcname=$1
typeset -i n=0
shift
for arg; do
((n++))
printf "%s:%d >%s<\n" "$funcname" $n "$arg"
done
}
test1() { show_params "${.sh.fun}" "$#"; test2 "$#"; }
test2() { show_params "${.sh.fun}" "$#"; }
test1 "" 'a string "with double quotes" in it'
test1:1 ><
test1:2 >a string "with double quotes" in it<
test2:1 ><
test2:2 >a string "with double quotes" in it<
Using your definition of test1, which builds up a string containing a command, adding double quotes around all the paramers, and then eval-ing the string, I get this result
$ test1 "" 'a string "with double quotes" in it'
1-1:
1-2: a string "with double quotes" in it
test2:1 ><
test2:2 >a string with<
test2:3 >double<
test2:4 >quotes in it<
That's because you're doing this:
eval "test2 \"\" \"a string \"with double quotes\" in it\""
# ......... A A A B B A
# A = injected quotes
# B = pre-existing quotes contained in the parameter

Correct usage of ~ parameter expansion flag?

According to man zshexpn (5.0.2):
~ Force string arguments to any of the flags below that follow within the parentheses to be treated as
patterns.
For example, using the s flag to perform field splitting requires a string argument:
% print -l ${(s:9:):-"foo893bar923baz"}
foo8
3bar
23baz
My reading of the ~ flag suggests that I should be able to specify a pattern in place of a literal string to split on, so that the following
% print -l ${(~s:<->:):-"foo893bar923baz"}
should produce
foo
bar
baz
Instead, it behaves the same as if I omit the ~, performing no splitting at all.
% print -l ${(s:<->:):-"foo893bar923baz"}
foo893bar923baz
% print -l ${(~s:<->:):-"foo893bar923baz"}
foo893bar923baz
Ok, rereading the question, it's the difference between this:
$ val="foo???bar???baz"
$ print -l ${(s.?.)val}
foo
bar
baz
And this:
$ val="foo???bar???baz"
$ print -l ${(~s.?.)val}
foo???bar???baz
It operates on the variable, i.e. the "argument" to the split (from your documentation quote). In the first example, we substitute literal ?, and in the second, we treat the variable as a glob, and there are no literal ?, so nothing gets substituted.
Still, though, split works on characters and not globs in the substution itself, from the documentation:
s:string:
Force field splitting (see the option SH_WORD_SPLIT) at the separator string.
So, it doesn't look like you can split on a pattern. The ~ character modifies the interpretation of the string to be split.
Also, from the same pattern expansion documentation you reference, it continutes:
Compare with a ~ outside parentheses, which forces the entire
substituted string to be treated as a pattern. [[ "?" = ${(~j.|.)array} ]]
with the EXTENDED_GLOB option set succeeds if and
only if $array contains the string ‘?’ as an element. The argument may
be repeated to toggle the behaviour; its effect only lasts to the end
of the parenthesised group.
The difference between ${(~j.|.)array} and ${(j.|.)~array} is that the former treats the values inarray as global, and the latter treats the result as a glob.
See also:
${~spec} Turn on the GLOB_SUBST option for the evaluation of spec; if
the ‘~’ is doubled, turn it off. When this option is set, the string
resulting from the expansion will be interpreted as a pattern anywhere
that is possible, such as in filename expansion and filename
generation and pattern-matching contexts like the right hand side of
the ‘=’ and ‘!=’ operators in conditions.
Here is a demo that shows the differences:
$ array=("foo???bar???baz" "foo???bar???buz")
$ [[ "foo___bar___baz" = ${(~j.|.)array} ]] && echo true || echo false
false
$ [[ "foo___bar___baz" = ${(j.|.)~array} ]] && echo true || echo false
true
And for completeness:
$ [[ "foo___bar___baz" = ${(~j.|.)~array} ]] && echo true || echo false
true

side effect of zsh echo?

For some reason this script will work with all of the 'echo's at the end, but without them $wall is an empty string. This seems like really odd behaviour.
#!/bin/zsh
if [ ! -n "$1" ] ; then
files=(~/pictures/backgrounds/*jpg)
else
while [ $1 ] ; do
files+=(`echo $1/*jpg`)
shift
done
fi
echo $files
N=${#files}
echo $N
((N=RANDOM%N))
echo $N
wall=${files[$N]}
echo $wall
cp $wall ~/wall.jpg
This code will sometimes fail because RANDOM%N can result in zero and zsh array indexes start with 1. You should use RANDOM%N+1 instead.
You can:
setopt ksharrays
to enable zero-based indexing.
From man zshoptions:
Emulate ksh array handling as closely as possible. If this
option is set, array elements are numbered from zero, an array
parameter without subscript refers to the first element instead
of the whole array, and braces are required to delimit a sub‐
script (${path[2]}' rather than just$path[2]').

Resources