Curly brace expansion: from tcsh to zsh - zsh

In ~/.tcshrc, I have the following aliases:
alias trash 'echo ./*{~,.bak,.save} ./.*{~,.bak,.save} ./#*#'
alias trashit 'rm -f ./*{~,.bak,.save} ./.*{~,.bak,.save} ./#*#'
How can I implement the same in zsh? The curly brace expansion seems to fail unless all criteria are met.

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}.

How can I suppress zsh pattern escaping during string expansion?

I am trying to port this snippet of code from bash to zsh:
#! /usr/bin/env bash # change zsh
set -x
pattern="[ab]"
marks="a"
case "${marks}" in
${pattern})
echo match
;;
esac
This prints "match".
Bash replaces ${pattern} as is and then uses [ab] to match either 'a' or 'b'.
zsh seemingly escapes the '[]' characters and then uses \[ab\] to match this string verbatim.
I don't see a zsh option, how to change the behavior and hardcoding the pattern is not an option.
To make zsh treat a parameter value as a pattern, instead of a literal string, you need to use glob substitution:
local marks=a pattern='[ab]'
case $marks in
( $~pattern )
echo match
;;
esac

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.

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 ${#:$#}'
$

how to use sed from a tcl file

I'm trying to use the Unix "sed" command form within a tcl file, like this:
(to change multiple spaces to one space)
exec /bin/sed 's/ \+/ /g' $file
I also tried exec /bin/sed 's/ \\+/ /g' $file (an extra backslash)
none of the version work, and I get the error
/bin/sed: -e expression #1, char 1: Unknown command: `''
The command works fine when run from a linux terminal
What am I doing wrong?
What am I doing wrong?
What you're doing wrong is using ' (single quote) characters. They're not special to Tcl at all. The equivalent in Tcl is enclosing a word in {braces}; it gives no special treatment at all to the characters inside. Thus, what you seek to do would be:
exec /bin/sed {s/ +/ /g} $file
Mind you, if you're doing something more complex and the restriction of Tcl to whole-words being unquoted, then you might instead go for this:
exec /bin/sh -c "sed 's/ +/ /g' $file"
Or, real idiomatic Tcl just doesn't use sed for something this simple:
set f [open $file]
set replacedContents [regsub -all { +} [read $f] " "]
close $f
Use exec /bin/sed "s/\ +/\ /g" $file
The '\ ' tells TCL that there's an space there. Also using the '"' configures properly the string.

Resources