different shell behaviors of unmatched glob in zsh and bash - shell

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

Related

how to select all items in zsh?

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

Bash grep different outputs

I have a funny issue with grep. Basically, I am trying to match certain control characters in a file and get the count.
grep -ocbUaE $"\x07\|\x08\|\x0B\|\x0C\|\x1A\|\x1B" <file>
Funny enough, in CLI it matches all control characters and returns the correct count, but if I use it in a bash script, it doesn't match anything.
Any ideas what I am doing wrong?
Tested on: MacOS and CentOS - same issue.
Thank you for your help!
I think you should change your command to:
grep -cUaE $'[\x07\x08\x0B\x0C\x1A\x1B]' file
I removed the extra output flags, which get ignored when -c is present. I assume that you include -U and -a for a reason.
The other changes are to use $'' with single quotes (you don't want a double-quoted string here), and replace your series of ORs with a bracket expression, which matches if any one of the characters match.
Note that C-style strings $'' don't work in all shells, so if you want to use bash you should call your script like bash script.sh and/or include the shebang #!/bin/bash if it is executable. sh script.sh does not behave in the same way as bash script.sh.

Portably use special characters in parameter expansion, with or without double quotes

In all of the following shells:
bash
ksh
zsh
dash
and without a way to influence either the choice of shell or of shell
settings, my goal is to use the default string value (y!) whenever
the variable named x is unset or null. I.e., I want the shell to
perform parameter expansion producing (y!) where y stands for some
text not involving any special characters. So, naively,
${x:-(y!)}
There may be double quotes. A number of differences become apparent, then, among the shells
interpreting this or a variation. In an attempt to
approach portability, I tried variants adding zero to three \s. I then also
tried the effects of spaces, but I'll exclude that for now.
${x:-(y!)}, ... , ${x:-\(y\!\)}
One of the differences shown by the shells would, I guess, be related to a shell's
overloading of ! for history events. (Try !echo in zsh.)
Other differences remain when a variant occurs between double quotes.
A solution would ideally work between double quotes, as I cannot really
expect a scheme not to be used in this context.
E.g., given comand echo "$0: ${x:-\(y\!\)}", i.e., with all of (, !,
and ) preceded by \, the shells respond:
-bash: \(y\!\)
ksh: \(y\!\)
zsh: \(y!\)
dash: \(y\!\)
So, the Z Shell begs to differ. It turns out that the following seems
to be working alike in bash, ksh, zsh, and dash:
$ echo "${x:-$(echo \(y\!\))}"
(y!)
But involving another command substitution (echo, even if
built-in(?)) seems overkill. Is there a better way? At all? Am I
missing something?
(Zsh seems to use !{...}, so not \, to “insulate a history
reference from adjacent characters (if necessary)” (zshexpn(1)). Maybe that explains why it responds differently?)
Using the octal code for the ! char seems to work:
/bin/echo -e "'${x:-(y\0041)}'"
The builtin echo commands for the various shells are inconsistent, (two require the -e switch, but dash doesn't understand it), so /bin/echo is more reliable. Test the four shells:
unset x
for f in dash bash zsh ksh ; do
echo -en "$f:\t"
$f -c "/bin/echo -e \"'\${x:-(y\0041)}'\""
done
Output:
dash: '(y!)'
bash: '(y!)'
zsh: '(y!)'
ksh: '(y!)'

shell script exit with no match with question mark symbol

Why ./script.sh ? throws No match. ./script.sh is running fine.
script.sh
#!/bin/sh
echo "Hello World"
? is a glob character on UNIX. By default, in POSIX shells, a glob that matches no files at all will evaluate to itself; however, many shells have the option to modify this behavior and either pass no arguments in this case or make it an error.
If you want to pass this (or any other string which can be interpreted as a glob) literally, quote it:
./script.sh '?'
If you didn't use quotes, consider what the following would do:
touch a b c
./script.sh ? ## this is the same as running: ./script.sh a b c
That said -- the behavior of your outer shell (exiting when no matches exist, rather than defaulting to pass the non-matching glob expression as a literal) is non-default. If this shell is bash, you can modify it with:
shopt -u failglob
Note, however, that this doesn't really fix your problem, but only masks it when your current directory has no single-character filenames. The only proper fix is to correct your usage to quote and escape values properly.

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"

Resources