What is the meaning of each parameter for *(*ocNY1) from the shell command `echo`? - zsh

I could not find the proper place to look up for the parameter explanation for the below command.
echo *(*ocNY1)
After some tests, I discovered that *(*oc) prints executable files(file with x permission) from the current directory. And NY1 prints the first item of such. But I cannot find the manual for such options. Where can I find the definition/manual for the parameters of such?
Where can I lookup to see the explanation for each parameters for the pattern matching?
Is this glob pattern or regex that echo is using?
Sometimes it is really hard to take the first step if you do not know where you are heading.

*(*ocNY1) is a zsh glob pattern - see man zshexpn.
* is a glob operator that matches any string, including the null string.
The trailing (...) contains glob qualifiers:
* to match executable plain files
oc sort by time of last inode change, youngest first
N sets the nullglob option for the current pattern
Yn expand to at most n filenames

Related

Zsh glob: Get everything except stuff in a certin folder

Trying to find all files except those inside vendor/ folders, but why is this failing?
setopt extendedglob
for file in **/*~vendor/; do
done
See if this does what you're looking for:
setopt extendedglob
print -l ^vendor/**/*(.)
The *~ negation syntax usually needs parentheses in order to determine where the expression after the tilde ends. Your pattern is requesting all files and folders except those where the glob result name ends with vendor/. The glob result never includes the trailing slash, so you end up with all of the files and folders.
Adding parens will change the behavior of that pattern, but probably not in a useful way. This will result in a list of all of the directories where the last component is not vendor:
print -l **/(*~vendor)/
so x/y, x/y/vendor/z, and vendor/a will be included, but x/y/vendor will not.
The parentheses limit the 'not' pattern to just one piece of the path. In order to exclude matches at the top-level, the tested component needs to be at the front of the pattern:
print -l (*~vendor)/**/*
The very first pattern above uses the ^ syntax to produce the same results. The (.) glob qualifier in that pattern limits the globbing to plain files, so directories are not included.
Another variation that may be useful - this will exclude directories that have any component named vendor. It is similar to find -prune:
print -l (^vendor/)#*(.)
This will produce a list of all files except those in subdirectories with names like vendor/x, a/vendor and a/vendor/b.

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

How to substitute only the highest number in zsh?

I have a folder with materials for university study, sorted by semesters:
$ ls University
semester1 semester2 semester3 semester4
I'm trying to make one of them the named directory, and I want zsh to allways pointed to directory ending with highest number (so I don't have to update my directory shortcut every semester).
So far I found only the zsh expansion <->:
$ ls semester<->
semester1 semester2 semester3 semester4
but I cannot find a way to extract only the last dirname from that.
Any idea how I should proceed or what I should change?
latestSemester=`ls semester<-> | tail -1`
echo $latestSemester
actually this also works
latestSemester=`ls semester<->([-1])`
EDIT: Fixed the second line, whose first version missed brackets.
From the zsh manual
[beg[,end]]
specifies which of the matched filenames should be included in the returned list. The
syntax is the same as for array subscripts. beg and the optional end may be mathemat-
ical expressions. As in parameter subscripting they may be negative to make them
count from the last match backward. E.g.: ‘*(-OL[1,3])’ gives a list of the names of
the three largest files.

How can I implement the command 'ls' with wildcard, '*'?

EDIT #1 : I'm under the limit that all arguments are enclosed in two quotes, so that shell do not expand any argument with * to the corresponding path.
EDIT #2 : In order to retrieve directories such as */*, ../*, and dirA/*/file.out, How should I use iteration loop or recursive call?
I have just learned about the function fnmatch(). But I don't know start place.
There are many possible cases. I'm confused dealing with these all cases.
For example, Let me assume that executable program is a.out.
$./a.out -l */*
$./a.out -l ../*
$./a.out -l [file_name] [directory_name]
/* Since I also have to implement ls command with no wildcard. */
What should I do? Any advice would be awesome.
Thank you in advance.
Your problem is : shell replaces wildcard caracter * with all of the filenames matching the pattern.
Solution:
If you do not want to use this feature of bash, just put quotation marks around your command line arguments.
Calling your program that way will have the original arguments, containing wildcards.
After this, you can list all the filenames with their paths. For example using some recursive algorithm. Then you can apply some matching to these path string. (when visiting it)
If you want to be a good unix citizen, the rule is Don't do filename globbing unless you are writing a shell.
You want to write an ls-like program? Don't do any wildcard expansion. Don't treat "*" specially. Just treat your argv as a list of filenames. If your program handles these cases:
./a.out file1
./a.out file1 file2 file3
Then it will also handle
./a.out file*
correctly because the shell will do the expansion and your program won't need to know about it. And besides that, it will handle this:
zsh% ./a.out **/file<40-185>~file<90-100>(.mm-30OL[1,2])
which in zsh expanded glob syntax means: expand file40 through file185, except for file90 through file100, include only the ones that have been modified in the last 30 minutes, and use only the largest 2 files in the resulting set.
fnmatch is never going to do anything like that. But these fancy globs can be used with any command that just takes a filename list and doesn't care where it came from.
When you're in a situation where you can't take a list of filenames from the command line, then consider using fnmatch. ls isn't one of those situations.

Resources