Why zsh tries to expand * and bash does not? - bash

I just encountered the following error with zsh when trying to use logcat.
Namely, when typing:
adb logcat *:D
I get the following error in zsh
zsh: no matches found: *:D
I have to escape the * like :
adb logcat \*:D
While using bash, I do not get the following error.
Why would it be like this?

zsh warns you by default if you use a glob with no matches. Bash, on the other hand, passes the unexpanded glob to the app, which is a potential problem if you don't know for certain what will match (or if you make a mistake). You can tell zsh to pass the unevaluated argument like bash with setopt nonomatch:
NOMATCH (+3) <C> <Z>
If a pattern for filename generation has no matches, print an
error, instead of leaving it unchanged in the argument list.
This also applies to file expansion of an initial `~' or `='.
Or drop the argument instead with setopt NULL_GLOB:
NULL_GLOB (-G)
If a pattern for filename generation has no matches, delete the
pattern from the argument list instead of reporting an error.
Overrides NOMATCH.
Bash actually has the same option (setopt nullglob), and can emulate zsh with setopt failglob

bash does try to expand it - it's just that when it fails to match anything, it lets the * through to the program you're calling. zsh doesn't (at least by default).
You can make bash act like zsh by setting the failglob option. Conversely, you can make zsh work like the bash default by turning off the NOMATCH option.

In terms of adb, no need to escape with backslashes. You can try
adb logcat '*:I'
Or an environment variable
export ANDROID_LOG_TAGS="*:I"
adb logcat

Short answer is: disable this by setopt nonomatch
(You can put it to ~/.zshrc) For more options, see #Kevin's answer.

Related

zsh substituion - what's the difference between $VAR and ${VAR}?

I recently converted a shell script from bash to zsh and got a strange error. I had a command like
HOST="User#1.1.1.1"
scp "$BASE_DIR/path/to/file" $HOST:some\\path
This worked fine in bash, but zsh failed with a bad substitution. I fixed this by change $HOST to ${HOST}, but I'm curious as to why this was necessary. Also, strangely, I had a few such scp commands, and all of them "worked" except the first one. However, I ended up with a file called User#1.1.1.1 on my filesystem which was really unexpected. Why did this subtle change make such a big difference?
Two possible problems (1) Extra '$' at the beginning of the assignment, and (2) embedded spaces.
The first potential problem is the assignment in the style $var=foo. In zsh like in other sh-like engines (ksh, bash, ...), the assignment operation is VAR=value - no $.
The second potential problem are the spaces. No spaces are allowed between the variables name, the '=' and the value. Spaces in the value must be escaped (with quotes, or backslash)
Potential correction:
HOST=User#1.1.1.1
scp "$BASE_DIR/path/to/file" $HOST:some\\path
As chepner mentioned in the commments, zsh has modifiers that are added via :. So $HOST:some was interpreted as $HOST:s by zsh.
A list of modifiers can be found here: https://web.cs.elte.hu/local/texinfo/zsh/zsh_23.html

How to print regex pattern as string in terminal?

I'm trying to write a regex string in the terminal but zsh is interpreting this regex instead of just printing it. My shell code:
echo "((https?:\/\/(?:www\.|(?!www)))?[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})"
Current output:
zsh: event not found: www)))?[a
I already tried to use simple quotes, double quotes and no quotes.
If you type this command in a file and run as a script, it should be fine, unless you have explicitly enabled history expansion in your script. But then, you know what you are doing.
If you really literally hack such a huge command manually into an interactive shell, either turn off history expansion (by setopt nobanghist), or prefix your ! by a \ (unless the ! is already between single-quotes).
Example: Typing echo !www won't work, but typing echo \!www will.
If you never use history expansion, turning it off permanently would probably be the best choice.

macos: Bash shows commands instead files

Bash on my macOS shows all (probably) commands from $PATH when I double-TAB instead of files and folders. Same when I can use TAB once - it does not work at all. Commands like CD works fine.
I have no idea where the problem is - $PATH seems normal to me:
/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
~/.profile is this:
export PATH=/usr/local/php5/bin:$PATH
export PATH="$HOME/.yarn/bin:$PATH"
~/.bash_profile has only this:
export PATH="/usr/local/sbin:$PATH"
Same behavior under PhpStorm's terminal. So I believe, the problem is somewhere deeper.
Can you help, please?
Command-line completion in Bash is achieved through readline, a well-known utility library used by many GNU and other Open Source products.
Basic completion is using the {TAB} key, and the actual completion depends on context. If the text does not start with a special character then commands are listed if they match, only if commands don't match are filenames listed.
If completion can be achieved, enough characters are supplied to make the completion unique, then it will be done, otherwise a second {TAB} will give a list of possibilities.
From man bash (Completing):
Bash attempts completion treating the text as a variable (if the text
begins with $), username (if the text begins with ~), hostname
(if the text begins with #), or command (including aliases and
functions) in turn. If none of these produces a match, filename
completion is attempted.

Create shell alias with semi-colon character

I've noticed that I have a tendency to mistype ls as ;s, so I decided that I should just create an alias so that instead of throwing an error, it just runs the command I mean.
However, knowing that the semi-colon character has a meaning in shell scripts/commands, is there any way to allow me to create an alias with that the semi-colon key? I've tried the following to no avail:
alias ;s=ls
alias ";s"=ls
alias \;=ls
Is it possible to use the semi-colon as a character in a shell alias? And how do I do so in ZSH?
First and foremost: Consider solving your problem differently - fighting the shell grammar this way is asking for trouble.
As far as I can tell, while you can define such a command - albeit not with an alias - the only way to call it is quoted, e.g. as \;s - which defeats the purpose; read on for technical details.
An alias won't work: while zsh allows you to define it (which, arguably, it shouldn't), the very mechanism that would be required to call it - quoting - is also the very mechanism that bypasses aliases and thus prevents invocation.
You can, however, define a function (zsh only) or a script in your $PATH (works in zsh as well as in bash, ksh, and dash), as long as you invoke it quoted (e.g., as \;s or ';s' or ";s"), which defeats the purpose.
For the record, here are the command definitions, but, again, they can only be invoked quoted.
Function (works in zsh only; place in an initialization file such as ~/.zshrc):
';s'() { ls "$#" }
Executable script ;s (works in dash, bash, ksh and zsh; place in a directory in your $PATH):
#!/bin/sh
ls "$#"

bash tab completion without variable expansion?

Let's say I have these variables defined in my bashrc:
i='cgi-bin/internal';
e='cgi-bin/external';
f='cgi-bin/foo';
b='cgi-bin/bar';
ad='cgi-bin/admin';
#etc...
When I use the variable on the command line vim $i/edit_TAB it will expand the variable and the input on the command line becomes vim /www/productX/subdomain_x/cgi-bin/internal/edit_ (respective to whatever site I'm on) and then I TABTAB to get the possible completions.
That's fine, the functionality isn't the problem. It's just that it can get annoying to see the full path every time rather than just the value of the variable.
Is there a way to not expand the bash variables on the command line without compromising functionality?
Is it the bash completion that's doing this?
The desired outcome would be $i not expanding to it's value (visually) or $i expanding to a relative path rather than the full path.
You might try using zsh instead of bash. In zsh,
vim $i[tab]
expands $i to a relative path
(Also Oh My Zsh is great for customizing zsh)
I am not sure which other settings you use in your bash startup scripts, but for me the following bash command does the trick:
complete -r -v
shopt -u direxpand
-u: Disable (unset) shell optional behavior flag
direxpand:
If set, Bash replaces directory names with the results of word expansion when performing filename completion. This changes the contents of the readline editing buffer. If not set, Bash attempts to preserve what the user typed.
Check current status with shopt direxpand (or all options with shopt).
Re-enable with shopt -s direxpand
Soure:
https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Using
shopt -u progcomp
worked for me, after this the tab did not expand variables anymore.
A shopt doc
https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html

Resources