The code below seems to be working correctly:
#!/bin/zsh
zparseopts -D -E -A opts f -foo
if [[ -n ${opts[(ie)-f]} || -n ${opts[(ie)--foo]} ]]; then
echo "foo is set."
else
echo "foo is not set."
fi
~/tmp > ./args.sh
foo is not set.
~/tmp > ./args.sh -f
foo is set.
~/tmp > ./args.sh --foo
foo is set.
What does the syntax for the index of opts mean i.e. (ie)-f? Is there some documentation where I can learn more about this? I don't even know what to search for to learn more about this kind of indexing.
My bad - I found it in the zsh manual here. It's explained in section 5.4.2 Using Associative Arrays.
To explain, it seems like this is a part of parameter substitutions in zsh. I don't know if it applies to bash as well.
This allows you to use certain "parameters" to perform some functionalities.
The syntax is to include the parameters within parentheses and prefix the specific part of the object that you want to substitute, modify etc.
For example, taking opts in my question,
echo ${opts}
prints the values of the associative array.
We have the parameter k which signifies the keys and v which signifies values. This can be used as follows:
echo ${(k)opts} # print only the keys
echo ${(kv)opts} # print the keys and values
To answer the main part of my question, what does (ie)-f mean, these are parameters that apply to the index of the associative array. Looking at the manual I had linked to, here is what i does - it searches for the key and returns the key instead of the value.
Explanation from the manual:
If instead of an ordinary subscript you use a subscript preceded by the flag (i), the shell will search for a matching key (not value) with the pattern given and return that. This is deliberately the same as searching an ordinary array to get its key (which in that case is just a number, the index), but note this time it doesn't match on the value, it really does match, as well as return, the key
And with regards to e - this seems a bit more complicated. But reading through the manual, it seems like this further evaluates the value if necessary i.e. in the event that it's not a constant.
Here is an example:
bar=4
foo='$bar'
> echo $foo
$bar
> echo ${(e)foo}
4
So combining the two together (ie) in my question returns the key and also expands it if necessary.
Related
I reduced my problem to this minimal example:
# This is script myscript
echo $ZSH_VERSION
zparseopts -D vxa:=opt_a vx:=opt_b
echo $1
I'm calling the script with
zsh myscript -vxa xx -vx zz a
I would expect that after processing the options, $1 would output a, but I get
5.8
xx
being printed. Why is this the case?
I am aware about a problem with my script, which is that the option vx is a prefix of vxa, and when I modify the script to
zparseopts -D vxa:=opt_a vxb:=opt_b
and call it accordingly, I indeed get the expected result (a) in the output. So, I can fix my script by just renaming the options so that neither one is a prefix of a different option.
However, I also would like to understand, why I see the xx with my original code. There is no error in the invocation (if I replace the -D by -F, I don't get an error message). I could understand it, if my original script would simply fill the wrong variable (taking vx as an abbreviation for vxa), but I don't understand, why the script silently stops parsing after consuming the -vxa option, but not even picking up the mandatory argument xx. What is going on here, and why?
Yes it's due to the overlapping names + ambiguity regarding how -vxa should be parsed vs -vx a (the issue being that both are being taken as a -vx option with option-arg a - its mandatory arg has been consumed, and the unhyphenated xx looks like a non-option so parsing stops without any error).
man zshmodules has some useful info (bold emphasis added):
In all cases, option-arguments must appear either immediately following the option in the same positional parameter or in the next one. ...
When the names of two options that take no arguments overlap, the longest one wins, so that parsing for the specs -foo -foobar (for example) is unambiguous. However, due to the aforementioned handling of option-arguments, ambiguities may arise when at least one overlapping spec takes an argument, as in -foo: -foobar. In that case, the last matching spec wins.
If the specs are swapped around so that the shorter of the overlapping names are placed before longer ones (i.e. zparseopts -D vx:=opt_b vxa:=opt_a) it should work as you expect. For example:
#!/bin/zsh -
echo $ZSH_VERSION
zparseopts -D vx:=opt_b vxa:=opt_a
print -r -- arg:$^# opt_a:$^opt_a opt_b:$^opt_b
$ zsh thatScript -vxa xx -vx zz a
5.9
arg:a opt_a:-vxa opt_a:xx opt_b:-vx opt_b:zz
What controls the environment to know to split by space in zsh?
I'm sure it's something simple but in all my searching have yet to figure it out what controls it.
Trying to loop over items in a space-separated string like so:
s='foo bar baz'
for i in $s; do
echo "$i END"
done
# foo bar baz END
# ---
s='foo bar baz'
a=( $s )
echo ${a[0]} # (empty)
echo ${a[1]} # foo bar baz
# ---
s='foo bar baz'
IFS=' ' read a <<< $s
for i in "${a[#]}"; do
echo "$i END"
done
# foo bar baz END
The different methods work via sh and bash, but in a shell with oh-my-zsh I'm unable to separate by space, getting the results above. May not be oh-my-zsh - but looking to understand what drives this.
Working example from bash:
s='foo bar baz'
for i in $s; do
echo "$i END"
done
# foo END
# bar END
# baz END
Zsh and bash are two different programming languages. They're similar, but not identical. In bash, and more generally in Bourne-style shells (sh, dash, ksh, …), an unquoted variable expansion $foo does the following:
Take the value of the variable foo, which is a string. (If there is no variable foo, take the empty string.)
Split the string into whitespace-separated parts. (More generally, the value of the IFS variable determines how the string is split; I won't go into all the details here.) The result is a list of strings.
For every element in the list, if it is a globbing pattern, i.e. if it contains at least one wildcard character *?\[ (and possibly more depending on some shell options), and that pattern matches at least one file name, then the element is replaced by the list of matching file names. Elements that don't contain any wildcard character, and elements that contain a wildcard character but don't match any file name, are left alone. The result is again a list of strings.
Zsh is mostly a Bourne-style shell, but it has some differences, and this is the main one: $foo has the following, simpler behavior.
Take the value of the variable foo, which is a string. (If there is no variable foo, take the empty string.)
If this results in an empty word, this word is eliminated. (So for example $foo$bar is only eliminated if both foo and bar are empty or unset.)
Note that in sh or bash, $foo only works to split a string if it doesn't contain any wildcard character or if globbing is disabled with set -f.
To split a string at whitespace in zsh, there are two simple methods:
Use the = parameter expansion specified to apply IFS word splitting. For example $=foo splits at whitespace as determined by IFS.
Use the p parameter expansion flag. For example ${(p: :)foo} splits at spaces (not tabs or newlines).
This has nothing to do with oh-my-zsh, which is a plugin to configure zsh for interactive use.
Just ${(p: :)foo} didn't work for me, and was giving a a zsh: error in flags error. After reading Parameter-Expansion doc, I see that it should be ${(ps: :)foo}. Even the flag p explanation in the doc uses the additional s flag.
The p flag doc says:
p :
Recognize the same escape sequences as the print builtin
in string arguments to any of the flags described below
that follow this argument.
So what I ended up using was just ${(s: :)foo}. See the example for the behavior where only space is used as the separator, contiguous spaces are treated as one, while tabs and newlines are preserved as is:
> FRUITS="apple\tbanana grapes orange passion_fruit\nwatermelon"
> for F in ${(ps: :)FRUITS}; do echo "GOT: <$F>"; done
GOT: <apple banana>
GOT: <grapes>
GOT: <orange>
GOT: <passion_fruit
watermelon>
I am writing a zsh script, which is invoked with a variable number of arguments, such as
scriptname a b c d filename
Inside the script, I want first to loop over the arguments (except the last one) and process them, and finally do something with the processed data and the last argument (filename).
I got this working, but am not entirely happy with my solution. Here is what I came up with (where process and apply are some other scripts not relevant to my problem):
#!/bin/zsh
set -u
x=""
filename=$#[-1]
# Process initial arguments
for ((i=1; i<$#; i++))
do
x+=$(process ${#[$i]})
done
apply $x $filename
I find the counting loop too cumbersome. If filename where the first argument, I would do a shift and then could simply loop over the arguments, after having saved the filename. However I want to keep the filename as the last argument (for consistency with other tools).
Any ideas how to write this neatly without counting loop?
You can slice off the last argument from the original list and save them into an array, if thats an option
args=("${#:1:$# -1}")
for arg in "${args[#]}"; do # iterate over all, except the last
printf '%s\n' "$arg"
done
Using the array as a placeholder is optional as you can iterate over the arguments slice directly i.e. for arg in "${#:1:$# -1}"; do. The syntax is even available in bash also.
As pointed out by chepner's comment, you could use a zsh specifc syntax as
for arg in $#[1,-2]; do
printf '%s\n' "$arg"
done
My zshenv file has a bunch of lines like
if [[ -d "$HOME/bin" ]]; then
path=($HOME/bin $path)
fi
I thought I’d try to factor this pattern out into a function. I replaced it with
function prepend_to_path_if_exists() {
if [[ -d $1 ]]; then
path=($1 $path)
fi
}
prepend_to_path_if_exists("$HOME/bin")
but this gives the error
/Users/bdesham/.zshenv:8: missing end of string
where line 8 is the one where I’m calling prepend_to_path_if_exists. What exactly is causing this error, and how can I make this function work? I’m using zsh 5.0.5 on OS X 10.10.1.
You could call functions as with usual command executions like this (without ()):
prepend_to_path_if_exists "$HOME/bin"
It seems that zsh try to expand the glob prepend_to_path_if_exists(…) rather than to call the function.
TL;DR: Prepending emelemnts to $path would be accomplished by a little cryptic way:
(I'm not quite sure that the below form is preferable for anyone though.)
# `typeset -U` uniqify the elements of array.
# It could be good for $path.
typeset -U path
# prepending some paths unconditionally,
path[1,0]=(\
$HOME/bin \
$HOME/sbin \
)
# then filtering out unnecessary entries afterward.
path=(${^path}(-/N))
The $path[x,0]=… is prepending(splicing) element(s) to array taken from the below:
So that's the same as VAR[1,0]=(...) ? It doesn't really "look" very
much like prepend to me.
-- Greg Klanderman (http://www.zsh.org/mla/workers/2013/msg00031.html)
The ${^path}(-/N) expands the glob qualifires -/N on the each $path elements.
(Without ^ in the parameter expansion, the last elements of array will be evaluated, so it is mandatory in this case.)
The glob qualifires -/N means that "symbolic links and the files they point to"(-) the "directory"(/). And when it does not match anything do not raise errors (N).
In short, it would keep exsisting directories only for $path.
i am beginner in writing scripts in csh/tcsh so that's why i need you help. I have to find out if arguments of my script are written correctly on stdin.
I have some script for example called 'first_script' that must have arguments in this form:
first_script -d (and this is my problem) ---> how can i find out, if there's number (integer - not only digit) after -d argument?
Thanks a lot for helping me.
Processes can only pass strings as arguments, so what you will get will always be a string. It's up to you to interpret the value as what you need (e. g. an integer).
In your case I think checking if the given string consists solely of digits would solve your issue. There are many ways to do this check, but here is my favorite:
if ( "$1" == "-d" ) then
expr "$2" : '[0-9]*$' > /dev/null && echo "We have a number" || echo "We have a non-number"
endif