Can zparseopts differentiate an argument list that contains '--'? - zsh

I'm trying to use zsh's zparseopts builtin to process some shell script arguments. The argument list may contain -- to indicate that subsequent values are regular inputs and not options, even if they begin with a hyphen.
When zparseopts modifies the argument list, it always removes the double hyphen. This makes it difficult to determine if an argument starting with a hyphen is an error.
A demonstration; -a and b are items to be processed:
set -- -x -- -a b
print -- $#
# => -x -- -a b
zparseopts -D -a opts -- x
print -- $#
# => -a b
In this next invocation, -a is an invalid option. But the zparseopts result is the same, so there isn't a way to find the error:
set -- -x -a b
print -- $#
# => -x -a b
zparseopts -D -a opts -- x
print -- $#
# => -a b
This workaround makes a copy of any arguments after a --, and later compares the copy with the result from zparseopts:
items=(${${#[(re)--,-1]}[2,-1]})
zparseopts -D -a opts -- x
if [[ ${#items} -ne ${##} ]]; then
items=($#)
if [[ ${idx::=${items[(i)-*]}} -le ${#items} ]]; then
print "unknown option: [${items[idx]}] items: [$items]"
exit 1
fi
fi
The workaround does what I need, but it seems strange that zparseopts doesn't support this directly. Am I missing something?

Related

zsh: `declare -p` associative array does not print values

run-help typeset says:
-p [ n ]
If the -p option is given, parameters and values are
printed in the form of a typeset command with an assign-
ment, regardless of other flags and options. Note that
the -H flag on parameters is respected; no value will be
shown for these parameters.
Note it says parameters and values above.
If it do:
% typeset -p ZPLGM
typeset -A ZPLGM
Note no key-values above, however they do exist:
% echo $ZPLGM[PLUGINS_DIR]
/home/ravi/.config/zsh/.zplugin/plugins
Why doesn't typeset -p work as I expect?
How do I get typeset to print a statement which, when executed, would recreate the array?
Because the variable ZPLGM is defined with -H option.
unset foo
typeset -AH foo=([bar]=123)
# ^----here
echo $foo[bar]
typeset -p foo
123
typeset -A foo
typeset has an option -H, as the manual explains:
-H Hide value: specifies that typeset will not display the
value of the parameter when listing parameters; the dis-
play for such parameters is always as if the `+' flag had
been given. Use of the parameter is in other respects
normal, and the option does not apply if the parameter is
specified by name, or by pattern with the -m option.
This is on by default for the parameters in the
zsh/parameter and zsh/mapfile modules. Note, however,
that unlike the -h flag this is also useful for non-spe-
cial parameters.
unset foo
typeset -A foo=([bar]=123)
echo $foo[bar]
typeset -p foo
123
typeset -A foo=( [bar]=123 )

meaning of $# in unix shell script [duplicate]

This question already has answers here:
What does $# mean in a shell script?
(8 answers)
Closed 5 years ago.
this is my piece of code:
##Checking swapspace if ignore option chosen will force creation of swap space
echo ; echo "[INFO]: Validating Swap space "
swapspace=`swapon -s|tail -1|awk '{print $3/1024}'`
if [ ${swapspace} -le 500 ]; then
echo $# |grep ignore>/dev/null
if [ `echo $?` -eq 0 ]; then
echo "[WARNING]: Swap space is below minimum requirement get the same fixed :${swapspace}
Proceeding with WorkAround. PLEASE GET IT FIXED AT THE EARLIEST"
dd if=/dev/zero of=/swapfile bs=2G count=4
chmod 0600 /swapfile; mkswap /swapfile; swapon /swapfile
export SWAPFLAG=1
else
echo "[ERROR]: Swap space is below minimum requirement get the same fixed :${swapspace}"
export SWAPFLAG=2
fi
fi
can someone please explain what is echo $# doing here?
PS: 'ignore is a hidden argument'
#Surbhi:
$# denotes all of the parameters passed to the script. eg--> ./test.ksh test1 test2 then $# will be equal to test1 test2.
If you run a shell script with arguments e.g:
./myscript goodbye cruel world
you can access those arguments within the script. In particular $# gives you them all as they were typed - e.g. if myscript.sh is
#! /bin/sh
echo $#
the above command simply prints goodbye cruel world to the terminal

Using grep to find a binary pattern in a file

Previously, I was able to find binary patterns in files using grep with
grep -a -b -o -P '\x01\x02\x03' <file>
By find I mean I was able to get the byte position of the pattern in the file. But when I tried doing this with the latest version of grep (v2.16) it no longer worked.
Specifically, I can manually verify that the pattern is present in the file but grep does not find it. Strangely, some patterns are found correctly but not others. For example, in a test file
000102030405060708090a0b0c0e0f
'\x01\x02' is found but not '\x07\x08'.
Any help in clarifying this behavior is highly appreciated.
Update: The above example does not show the described behavior. Here are the commands that exhibit the problem
printf `for (( x=0; x<256; x++ )); do printf "\x5cx%02x" $x; done` > test
for (( x=$((0x70)); x<$((0x8f)); x++ )); do
p=`printf "\'\x5cx%02x\x5cx%02x\'" $x $((x+1))`
echo -n $p
echo $p test | xargs grep -c -a -o -b -P | cut -d: -f1
done
The first line creates a file with all possible bytes from 0x00 to 0xff in a sequence. The second line counts the number of occurrences of pairs of consecutive byte values in the range 0x70 to 0x8f. The output I get is
'\x70\x71'1
'\x71\x72'1
'\x72\x73'1
'\x73\x74'1
'\x74\x75'1
'\x75\x76'1
'\x76\x77'1
'\x77\x78'1
'\x78\x79'1
'\x79\x7a'1
'\x7a\x7b'1
'\x7b\x7c'1
'\x7c\x7d'1
'\x7d\x7e'1
'\x7e\x7f'1
'\x7f\x80'0
'\x80\x81'0
'\x81\x82'0
'\x82\x83'0
'\x83\x84'0
'\x84\x85'0
'\x85\x86'0
'\x86\x87'0
'\x87\x88'0
'\x88\x89'0
'\x89\x8a'0
'\x8a\x8b'0
'\x8b\x8c'0
'\x8c\x8d'0
'\x8d\x8e'0
'\x8e\x8f'0
Update: The same pattern occurs for single-byte patterns -- no bytes with value greater than 0x7f are found.
The results may depend on you current locale. To avoid this, use:
env LANG=LC_ALL grep -P "<binary pattern>" <file>
where env LANG=LC_ALL overrides your current locale to allow byte matching. Otherwise, patterns with non-ASCII "characters" such as \xff will not match.
For example, this fails to match because (at least in my case) the environment has LANG=en_US.UTF-8:
$ printf '\x41\xfe\n' | grep -P '\xfe'
when this succeeds:
$ printf '\x41\xfe\n' | env LANG=LC_ALL grep -P '\xfe'
A?

Combining file tests in Zsh

What is the most elegant way in zsh to test, whether a file is either a readable regular file?
I understand that I can do something like
if [[ -r "$name" && -f "$name" ]]
...
But it requires repeating "$name" twice. I know that we can't combine conditions (-rf $name), but maybe some other feature in zsh could be used?
By the way, I considered also something like
if ls ${name}(R.) >/dev/null 2>&1
...
But in this case, the shell would complain "no matches found", when $name does not fulfil the criterium. Setting NULL_GLOB wouldn't help here either, because it would just replace the pattern with an empty string, and the expression would always be true.
In very new versions of zsh (works for 5.0.7, but not 5.0.5) you could do this
setopt EXTENDED_GLOB
if [[ -n $name(#qNR.) ]]
...
$name(#qNR.) matches files with name $name that are readable (R) and regular (.). N enables NULL_GLOB for this match. That is, if no files match the pattern it does not produce an error but is removed from the argument list. -n checks if the match is in fact non-empty. EXTENDED_GLOB is needed to enable the (#q...) type of extended globbing which in turn is needed because parenthesis usually have a different meaning inside conditional expressions ([[ ... ]]).
Still, while it is indeed possible to write something up that uses $name only once, I would advice against it. It is rather more convoluted than the original solution and thus harder to understand (i.e. needs thinking) for the next guy that reads it (your future self counts as "next guy" after at most half a year). And at least this solution will work only on zsh and there only on new versions, while the original would run unaltered on bash.
How about make small(?) shell functions as you mentioned?
tests-raw () {
setopt localoptions no_ksharrays
local then="$1"; shift
local f="${#[-1]}" t=
local -i ret=0
set -- "${#[1,-2]}"
for t in ${#[#]}; do
if test "$t" "$f"; then
ret=$?
"$then"
else
return $?
fi
done
return ret
}
and () tests-raw continue "${#[#]}";
or () tests-raw break "${#[#]}";
# examples
name=/dev/null
if and -r -c "$name"; then
echo 'Ok, it is a readable+character special file.'
fi
#>> Ok, it is...
and -r -f ~/.zshrc ; echo $? #>> 0
or -r -d ~/.zshrc ; echo $? #>> 0
and -r -d ~/.zshrc ; echo $? #>> 1
# It could be `and -rd ~/.zshrc` possible.
I feel this is somewhat overkill though.

How do I pass arguments to shell script?

I have a batch file like this:
java temptable %1 %2
I need the equivalent shell script for the above. I will pass the arguments to the shell script and that should be passed to temptable.
For bash (which is one shell, but probably the most common in the Linux world), the equivalent is:
java temptable $1 $2
assuming there's no spaces in the arguments. If there are spaces, you should quote your arguments:
java temptable "$1" "$2"
You can also do:
java temptable $*
or:
java temptable "$#"
if you want all parameters passed through (again, that second one is equivalent to quoting each of the parameters: "$1" "$2" "$3" ...).
#!/bin/bash
# Call this script with at least 3 parameters, for example
# sh scriptname 1 2 3
echo "first parameter is $1"
echo "Second parameter is $2"
echo "Third parameter is $3"
exit 0
Output:
[root#localhost ~]# sh parameters.sh 47 9 34
first parameter is 47
Second parameter is 9
Third parameter is 34

Resources