zsh: unknown file attribute - zsh

I have the following function in my .zshrc which, in theory, allows me to write a commit message without needing quotation marks.
cm(){
git commit -m "$#"
}
When I run it (cm foo bar), I get the following error:
zsh: unknown file attribute
Does $# mean the same thing in zsh as it does in bash?

Accoring to this article, * and # both contain an array of the positional parameters.
The parameters *, # and argv are arrays containing all the positional parameters; thus $argv[n], etc., is equivalent to simply $n.
And...
A subscript of the form [*] or [#] evaluates to all elements of an array; there is no difference between the two except when they appear within double quotes. "$foo[*]" evaluates to "$foo[1] $foo[2] ...", whereas "$foo[#]" evaluates to "$foo[1]" "$foo[2]" ....

Related

Sort list of files in different directories with zsh

Assume I have the following directory/file structure
dirA/1fileAA.zsh
dirA/99fileAB.zsh
dirB/2fileBA.zsh
dirB/50fileBB.zsh
dirB/subdirA/20fileBAA.zsh
which I want to have ordered by the numbers the filenames begin with, ignoring any directories, so I get
dirA/1fileAA.zsh
dirB/2fileBA.zsh
dirB/subdirA/20fileBAA.zsh
dirA/99fileAB.zsh
dirB/50fileBB.zsh
using just built-in zsh functionality.
What would be the best way to achieve this?
I could think of rewriting strings sort and write them back?
Or better try to create an associated array and sort by keys?
I'm still a zsh and want to avoid digging into the wrong direction, too much.
Here is one way to accomplish this using only zsh builtins. The function prepends the filename to the front of each path for sorting and then removes it:
function sortByFilename {
local -a ary
printf -v ary '%s/%s' ${${argv:t}:^argv}
print -l ${${(n)ary}#*/}
}
With your example directory setup, it can be invoked from the parent directory of dirA and dirB with:
sortByFilename **/*.zsh
Testing it:
sortByFilename \
dirA/1fileAA.zsh \
dirA/99fileAB.zsh \
dirB/2fileBA.zsh \
dirB/50fileBB.zsh \
'/leadslash/42 and spaces' \
dirB/subdirA/20fileBAA.zsh
Result:
dirA/1fileAA.zsh
dirB/2fileBA.zsh
dirB/subdirA/20fileBAA.zsh
/leadslash/42 and spaces
dirB/50fileBB.zsh
dirA/99fileAB.zsh
The pieces:
printf -v ary <fmt> ...: runs printf with the format string, and assign the results to the ary array. Each iteration of the format string will become another element in the array.
%s/%s: the format string. This will concatenate two strings with a slash separator.
If there are more values than in the input than specifiers in the format string, printf will repeat the format pattern. So here, it will pull pairs (of filename/pathname) from the input array.
${${argv:t}:^argv}: this will produce an array alternating with filenames and full paths, i.e. (file1 path1 file2 path2 ...)
${ :^ }: zsh parameter expansion that will zip two arrays to create the alternating filenames and paths.
${argv:t}: array of filenames. Built using the function positional parameters in argv, and the :t modifier, which returns the filename component for each element in the array.
argv: array of full paths.
print -l: print each element of the input on a separate line.
${${(n)ary}#*/}: the final sorted list of paths.
${(n)ary}: Returns the array sorted numerically, using the n parameter expansion flag. At this point, each element in ary is the concatenation of the filename, a slash, and the input path.
The n flag works here because of the filename pattern; it will sort by decimal value instead of lexically within a common / empty prefix, e.g. foo1 foo3 foo12.
${ #*/}: Removes the pattern */ from the front of each element in the array. This deletes the prefix that was being used for sorting, leaving the original path.
local -a ary: declares an array variable. This is used as an indicator to printf -v to split its output.
It's possible to eliminate this line and make the function shorter and a bit more cryptic by (re-/mis-/ab)using the pre-declared array argv.
function sortByFilename {
printf -v argv %s/%s ${${argv:t}:^argv}
print -l ${${(n)argv}#*/}
}
Edit - a single-line version:
(){print -l ${"${(n0)$(printf '%s/%s\0' ${${argv:t}:^argv})}"#*/}} **/*.zsh
Including this simply because one-liners are fun to create, not because it's recommended. With the anonymous function, process substitution, and additional parameter expansion flags, this is less readable and possibly less efficient than the function above.

noglob function then use ls with param?

I just want to pass a glob through and then use it against ls directly. The simplest example would be:
test() { ls -d ~/$1 }
alias test="noglob test"
test D*
If I simply run ls D in my home directory: it outputs three files. but if I run the snippet provided, I get "/Users/jubi/D*": No such file or directory. What should I be doing? thanks!
The authoritative and complete documentation of Zsh expansion mechanism is located at http://zsh.sourceforge.net/Doc/Release/Expansion.html.
Here's the reason your version doesn't work:
If a word contains an unquoted instance of one of the characters ‘*’, ‘(’, ‘|’, ‘<’, ‘[’, or ‘?’, it is regarded as a pattern for filename generation, unless the GLOB option is unset.
emphasis mine. Your glob operator, generated by parameter expansion, isn't considered unquoted.
You need the GLOB_SUBST option to evaluate the parameter expansion result as a glob pattern. a setopt globsubst, unsetopt globsubst pair works, of course, but the easiest way is to use the following pattern specifically for this purpose:
${~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.
In nested substitutions, note that the effect of the ~ applies to the result of the current level of substitution. A surrounding pattern operation on the result may cancel it. Hence, for example, if the parameter foo is set to *, ${~foo//\*/*.c} is substituted by the pattern *.c, which may be expanded by filename generation, but ${${~foo}//\*/*.c} substitutes to the string *.c, which will not be further expanded.
So:
t () { ls -d ~/${~1} }
alias t="noglob t"
By the way, test is a POSIX shell builtin (aka [). Don't shadow it.

applying zsh qualifiers on array elements or directly on a result of a command substitution

I did
a=( pacman -Qlq packagename )
to put files belonging to package into array
Why is this printing only the frist match, and how to print them all in zsh:
print -l ${a[(r)*i*]}
Also, how to apply zsh qualifiers on all array elements, say to list files
only via (.)
Is there an easier way to skip intermediary array in this process,
in a way to have qualifier specified on a result of a command substition?
As per documentation the subscript flag (r) will only return the first matching array element.
In order to get all matching elements you can use the {name:#pattern} parameter expansion, which removes any element maching pattern from the expansion. In order to remove the non-matching elements you can either use the flag (M) or negate the pattern with ^ (this requires the EXTENDED_GLOB option to be enabled):
print -l ${(M)a:#*i*}
setopt extendedglob
print -l ${a:#^*i*}
You can skip explicitly creating an intermediary array by just using the parameter expansion on the command substitution ($(...)) directly:
print -l ${(M)$(pacman -Qlq packagename):#*i*}
It seems that globbing qualifiers do not work with patterns inside parameter expansions. But you can enable the RC_EXPAND_PARAM option to expand every single array element within a word instead of the whole array. So foo${xx}bar with x=(a b c) will be expanded to fooabar foobbar foocbar instead of fooa b cbar. You can enabley it either globally with setopt rcexpandparam or for a specific expansion by wrapping it in ${^...}. This way you can add a glob qualifier to each element of the filtered array. To print only elements that are paths to files, you can use
print -l ${^${(M)$(pacman -Qlq packagename):#*i*}}(.N)
This essentially takes each path and attaches (.N) as glob qualifier (which works, even though there are no globs). The resulting patterns are then evaluated as part of filename generation. . tells zsh to only match plain files. N enables the NULL_GLOB option for these patterns, otherwise the command would abort with an "no matches found" error, if it encounters a pattern that is not a plain file (e.g. /usr is a directory, so /usr(.) does not match any plain file on your system.).

Passing param to function in ksh script

The parameter passed is changed when printed from inside the function
print "param: $FILEPREFIX" 1>&2; #return finyear*
func_copy $FILEPREFIX
then in function
function func_copy
{
fp=$1
print "param: $fp" 1>&2; #returns finyear.scr which is a script file name
what would i be doing wrong here
When you invoke func_copy $FILEPREFIX and FILEPREFIX contains finyear*, the shell expands the variable and then does wild-card matching, so the name finyear* is changed to finyear.scr in your directory. To avoid the expansion, enclose the name in double quotes:
func_copy "$FILEPREFIX"
(Using double quotes around a variable expansion is usually, but not always, a good idea.)
See the Bash manual on shell expansions for the sequence of operations in Bash. The POSIX shell (sh) has similar rules, and Korn shell will likewise be similar — they all have a common ancestor, the Bourne shell.

Changing the global “path” from within a function?

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.

Resources