how to select all items in zsh? - bash

in bash, we can say
dnf install <some-pkg>-*
or
rm -rf *.jpg
but when I'm using zsh, it doesn't do anything with the star (*) character!
how can I do those commands on zsh?!

These are two different uses of globs. With dnf, you expect dnf itself to expand the pattern against the available packages. With rm, you are expecting the shell to expand the pattern against the files in the current directory and passing the resulting names to rm.
In bash, the default behavior is for a pattern that doesn't match anything to be treated as a literal string. That's why the dnf example works in bash: when there are no local files matching <some-pkg>-*, the literal string is passed to zsh.
The solution is to quote strings that you intended to be treated literally, instead of relying on your shell's treatment of unmatched patterns. The following will work as intended in both shells:
dnf install "<some-pkg>.*"
rm -rf *.jpg
In both shells, you can change how unmatched patterns are treated. To make zsh act like the bash default, use
setopt NO_NOMATCH
To make bash behave like the zsh default, you could use
shopt -s failglob

Probably, the globbing is disabled on your zsh. Enable it using the below command in zsh:
setopt GLOB

Related

different shell behaviors of unmatched glob in zsh and bash

When I use a miniconda environment, I want to use this command to install a python package.
pip install recommenders[examples]
In zsh, it returns a error.
zsh: no matches found: recommenders[example]
In bash, it is successful and installs the package.
Someone told me that zsh treats unmatched glob as an error, but bash treats it as a literal text.
How should I change .zshrc in order to make zsh behaves like bash at this point, aka treats unmatched glob in "[]" as a literal text?
Thanks for any suggestion in advance.
(Besides, I am a beginner to zsh. I even don't know how to google it. What is the keyword of this problem? thx)
Use setopt NONOMATCH to leave an unmatched glob as literal text.
Similarly, use setopt NULL_GLOB to treat an unmatched glob as if it hadn't been used at all.
Some examples:
% print recommenders[examples]
zsh: no matches found: recommenders[example]
% setopt NONOMATCH
% print recommenders[examples]
recommenders[examples]
% print recommenders[examples]
%
Both options are documented under man zshoptions. (Use man zsh to see what man pages are available, or man zshall to see all of them at once.)

Can I do a negated wildcard on the command-line? [duplicate]

This question already has answers here:
How can I use inverse or negative wildcards when pattern matching in a unix/linux shell?
(11 answers)
Closed 2 years ago.
On the command-line *tmp* will match all files with names containing "tmp". Is there a quick way to do the reverse, i.e. match all files with names that don't contain "tmp"?
I figured out how to get ls to do it (ls -I "*tmp*"), but that doesn't help if I want to use some other command rather than ls. Is there a general method?
I forgot to note: I'm using zsh.
It depends on your shell.
In ksh, you can use this:
!(*tmp*)
In bash, the same thing works if you first enable the feature with shopt -s extglob.
In zsh, you can enable the same syntax with setopt ksh_glob, but there's a conflict with another zsh feature that you have to disable with setopt no_bare_glob_qual before the above will actually work. Alternatively, you can just use zsh's native version via setopt extended_glob; the equivalent of the above expression then looks like this:
^*tmp*
When using bash, you can enable extglob with:
shopt -s extglob
then you can use:
!(*tmp*)
that will negate your wildcard condition.
you can finally disable extglob with shopt -u extglob.

Bash wildcard pattern using `seq`

I am trying the following command:
ls myfile.h1.{`seq -s ',' 3501 3511`}*
But ls raises the error:
ls: cannot access myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511}*: No such file or directory
Seems like ls is thinking the entire line is a filename and not a wildcard pattern. But if I just copy that command ls myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511}* in the terminal I get the listing as expected.
Why does typing out the command in full work, but not the usage with seq?
seq is not needed for your case, try
$ ls myfile.h1.{3500..3511}
if you want to use seq I would suggest using format option
$ ls $(seq -f 'myfile.h1.%g' 3501 3511)
but I don't think there is any reason to do so.
UPDATE:
Note that I didn't notice the globbing in the original post. With that, the brace extension still preferred way
$ ls myfile.h1.{3500..3511}*
perhaps even factoring the common digit out, if your bash support zero padding
$ ls myfile.h1.35{00..11}*
if not you can extract at least 3 out
$ ls myfile.h1.3{500..511}*
Note that the seq alternative won't work with globbing.
Other answer has more details...
karakfa's answer, which uses a literal sequence brace expansion expression, is the right solution.
As for why your approach didn't work:
Bash's brace expansion {...} only works with literal expressions - neither variable references nor, as in your case, command substitutions (`...`, or, preferably, $(...)) work[1] - for a concise overview, see this answer of mine.
With careful use of eval, however, you can work around this limitation; to wit:
from=3501 to=3511
# CAVEAT: Only do this if you TRUST that $from and $to contain
# decimal numbers only.
eval ls "myfile.h1.{$from..$to}*"
#ghoti suggests the following improvement in a comment to make the use of eval safe here:
# Use parameter expansion to remove all non-digit characters from the values
# of $from and $to, thus ensuring that they either contain only a decimal
# number or the empty string; this expansion happens *before* eval is invoked.
eval ls "myfile.h1.{${from//[^0-9]/}..${to//[^0-9]/}}*"
As for how your command was actually evaluated:
Note: Bash applies 7-8 kinds of expansions to a command line; only the ones that actually come into play here are discussed below.
first, the command in command substitution `seq -s ',' 3501 3511` is executed, and replaced by its output (also note the trailing ,):
3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,
the result then forms a single word with its prefix, myfile.h1.{ and its suffix, }*, yielding:
myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,}*
pathname expansion (globbing) is then applied to the result - in your case, since no files match, it is left as-is (by default; shell options shopt -s nullglob or shopt -s failglob could change that).
finally, literal myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,}* is passed to ls, which - because it doesn't refer to an existing filesystem item - results in the error message you saw.
[1] Note that the limitation only applies to sequence brace expansions (e.g., {1..3}); list brace expansions (e.g, {1,2,3}) are not affected, because no up-front interpretation (interpolation) is needed; e.g. {$HOME,$USER} works, because brace expansion results expanding the list to separate words $HOME, and $USER, which are only later expanded.
Historically, sequence brace expansions were introduced later, at a time when the order of shell expansions was already fixed.

What behavior can and should I expect of a shell with the command and glob "echo -?"?

If I want to match a file called "-f" or "-r" I might do something like
test.sh -?
And if I want to send the literal '-?' to a program as an argument I might do something like:
test.sh -\?
If no such file "-f" or "-r" or anything like it exists, then what should my shell do with
test.sh -?
Should it tell me that no file matches this pattern?
In bash, the default is to treat an unmatched pattern literally. If the nullglob option is set, an unmatched pattern "evaporates"; it is removed from command, not even expanding to the empty string.
In zsh, an unmatched pattern produces an error by default. Setting the nomatch option causes an unmatched pattern to be treated literally, and zsh also supports a nullglob option which causes unmatched patterns to disappear. There is also a cshnullglob option which acts like nullglob, but requires at least one pattern in a command to match, or an error is produced.
Note that POSIX specifies that if the pattern contains an invalid bracket expression or does not match any existing filenames or pathnames, the pattern string shall be left unchanged in sh.
ash, dash, ksh, bash and zsh all behave this way when invoked as sh.
POSIX specifies that if the pattern contains an invalid bracket expression or does not match any existing filenames or pathnames, the pattern string shall be left unchanged in sh.
ash, dash, ksh, bash and zsh all behave this way when invoked as sh.
You seem to be looking for the nullglob option, at least with Bash:
shopt -s nullglob
Without the nullglob option, an unmatched pattern is passed as its literal self to your program: the shell will pass -? to the script if there isn't a file that matches. With the nullglob option, unmatched patterns are replaced with nothing at all.
If no such pattern exists, the shell, by default, just returns the pattern your gave, including whatever * or ? characters you used. To determine whether the file actually exists, test it. Thus, inside your script, use:
[ -f "$1" ] || echo "no such file exists"

How to copy multiple files from a different directory using cp, variable and brackets?

My question is very similar to How to copy multiple files from a different directory using cp?
I don't want to use an explicit loop. Here is what I do:
$ FILES_TOOLS="fastboot,fastboot-HW.sh"
$ cp $HOME/tools/{$FILES_TOOLS} $TOP_DIR/removeme
cp: cannot stat `/home/johndoe/tools/{fastboot,fastboot-HW.sh}': No such file or directory
The files are present and destination is valid, because:
$ cp $HOME/tools/{fastboot,fastboot-HW.sh} $TOP_DIR/removeme
$ echo $?
0
I tried to remove the double quote from FILES_TOOLS, no luck.
I tried to quote and double quote {...}, no luck
I tried to backslash the brackets, no luck
I guess this is a problem of when the shell expansion actually occurs.
This answer is limited to the bash.
Prepend an echo to see what your cp command turns into:
echo cp $HOME/tools/{$FILES_TOOLS} $TOP_DIR/removeme
You have to insert an eval inside a sub-shell to make it work:
cp $( eval echo $HOME/tools/{$FILES_TOOLS} ) $TOP_DIR/removeme
I guess this is a problem of when the shell expansion actually occurs.
Yes. Different shells have different rules about brace expansion in relation to variable expansion. Your way works in ksh, but not in zsh or bash. {1..$n} works in ksh and zsh but not in bash. In bash, variable expansion always happens after brace expansion.
The closest you'll get to this in bash is with eval.
As long as the contents of the braces are literals, you can use brace expansion to populate an array with the full path names of the files to copy, then expand the contents of the array in your cp command.
$ FILES_TOOLS=( $HOME/tools/{fastboot,fastboot-HW.sh} )
$ cp "${FILES_TOOLS[#]}" $TOP_DIR/removeme
Update: I realized you might have a reason for having the base names alone in the variable. Here's another array-based solution that lets you prefix each element of the array with a path, again without an explicit loop:
$ FILES_TOOLS=( fastboot fastboot-HW.sh )
$ cp "${FILES_TOOLS[#]/#/$HOME/tools/}" $TOP_DIR/removeme
In this case, you use the pattern substitution operator to replace the empty string at the beginning of each array element with the directory name.

Resources