How do I require a minimum version of zsh? - zsh

I want to use a feature only present in newer versions of zsh:
[[ "$foo" =~ "regexp" ]] where regexp is a regular expression.
Is it possible to do this check?
Something like:
if [[ $ZSH_VERSION > 4.3.9 ]]; then
....
fi
Except that > won't work.
Ciao!

I just found is-at-least but I'm not sure if it is available in all/many versions of zsh.
Usage:
autoload -U is-at-least
if is-at-least 4.3.9; then
....
fi
It's described in zshcontrib.
Ciao!

Related

Changing a default Zsh completion function

I noticed that tab completion for the source command in Zsh tries to complete a LOT of files. Maybe everything in $PATH? I tried using a blank .zshrc file to make sure it wasn't anything in there.
ubuntu% source d
zsh: do you wish to see all 109 possibilities (16 lines)?
I did find this file that seems to control that: /usr/share/zsh/functions/Completion/Zsh/_source
#compdef source .
if [[ CURRENT -ge 3 ]]; then
compset -n 2
_normal
else
if [[ -prefix */ && ! -o pathdirs ]]; then
_files
elif [[ $service = . ]]; then
_files -W path
else
_files -W "(. $path)"
fi
fi
If I change the line in that last "else" statement from _files -W "(. $path)" to _files, it works the way I want it to. The tab completion only looks at files & directories in the current dir.
It doesn't seem like altering this file is the best way to go. I'd rather change something in my .zshrc file. But my knowledge of Zsh completions is a bit lacking and the searching I've done thus far hasn't led me to an answer for this.
Maybe everything in $PATH?
Yes, that is correct. It offers those, because source will search your the current dir and your $PATH for any file name you pass it.
To apply your change without modifying the original file, add this to your .zshrc file after calling compinit:
compdef '
if [[ CURRENT -ge 3 ]]; then
compset -n 2
_normal
else
_files
fi
' source
This tells the completion system to use the inline function you specified for the command source (instead of the default function).
Alternatively, to see file completions for the current dir only, you can type
$ source ./<TAB>

Zsh returning `<function>:<linenumber> = not found`

I used the have the following tmux shortcut function defined in a separate script and aliased, which worked fine but was messy. I decided to move it to my .zshrc where it naturally belongs, and encountered a problem I wasn't able to figure out.
function t () {re='^[0-9]+$'
if [ "$1" == "kill" ]
then
tmux kill-session -t $2
elif [[ "$1" =~ "$re" ]]
then
tmux attach-session -d -t $1
fi}
I source my .zshrc, call the function, and get:
t:1: = not found
I know the function is defined:
╭─bennett#Io [~] using
╰─○ which t
t () {
re='^[0-9]+$'
if [ "$1" == "kill" ]
then
tmux kill-session -t $2
elif [[ "$1" =~ "$re" ]]
then
tmux attach-session -d -t $1
fi
}
I'm assuming this is complaining about the first line of the function. I've tried shifting the first line of the function down several lines, which doesn't change anything except which line the error message refers to. Any clue what's going on? I haven't found anything relating to this specific issue on SO.
The command [ (or test) only supports a single = to check for equality of two strings. Using == will result in a "= not found" error message. (See man 1 test)
zsh has the [ builtin mainly for compatibility reasons. It tries to implement POSIX where possible, with all the quirks this may bring (See the Zsh Manual).
Unless you need a script to be POSIX compliant (e.g. for compatibility with other shells), I would strongly suggest to use conditional expressions, that is [[ ... ]], instead of [ ... ]. It has more features, does not require quotes or other workarounds for possibly empty values and even allows to use arithmetic expressions.
Wrapping the first conditional in a second set of square-brackets seemed to resolve the issue.
More information on single vs double brackets here:
Is [[ ]] preferable over [ ] in bash scripts?

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.

unix script error

I have the following line in my unix script file:
if [[ -f $DIR1/$FILE1 ] -a [ -f $DIR1/$FILE2 ]]; then
As clear the line checks for existence of two files in a directory and if both the files are present, some logic will be executed.
However, on running the script I am getting the following error on above line:
test_script: line 30: syntax error at line 54: `]' unexpected
line 54 is where above line is present.
What does this error mean ? Where am I wrong ?
Thanks for reading!
For the most common shells at least, [] are not like parentheses in C where you use then to group subexpressions.
What you need is something like (for bash):
if [[ -f $DIR1/$FILE1 && -f $DIR1/$FILE2 ]]; then
If you want help with a specific (non-bash) shell, you should let us know which one you're using.
There is no need of [] with -f.
if [ -f $DIR1/$FILE1 -a -f $DIR1/$FILE2 ]; then
Output:
shadyabhi#archlinux /tmp $ touch foo;touch foo2
shadyabhi#archlinux /tmp $ if [ -f "foo" -a -f "foo2" ]; then echo "Hello"; fi
Hello
shadyabhi#archlinux /tmp $
It's interesting that there are multiple answers explaining the subtle differences between [ and [[, but for some reason our culture seems to discourage people from providing the obvious solution. Stop using '[' entirely. Instead of '[', use test:
if test -f $DIR1/$FILE1 && test -f $DIR1/$FILE2; then
Test is cleaner syntax than '[', which requires a final ']' argument and continually confuses people into thinking that the brackets are part of the language. '[[' is not portable and confuses people who don't realize that many shells provide extra functionality that is non-standard. There is a case to be made that [[ can be more efficient than [, but if run-time performance is a problem in your shell, you probably shouldn't be solving the problem in sh.
You had extra [ and ]
if [ -f $DIR1/$FILE1 -a -f $DIR1/$FILE2 ]; then
Basically, you were mixing two syntax that aim to do the same thing: namely [ ] and [[ ]]. The former is more portable but the latter is more powerful; although the majority of shells you would come across do support [[ ]].
But better still is the following since you are already using the [[ ]] construct
if [[ -f $DIR1/$FILE1 && -f $DIR1/$FILE2 ]]; then
As #paxdiablo stated, you can use it this way:
if -f $DIR1/$FILE1 && -f $DIR1/$FILE2 ; then
or you can use it this way:
if -f $DIR1/$FILE || -f $DIR1/$FILE2 ;

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