How does the matcher-list arguments work in zsh zstyle completion? - zshrc

I'm trying to configure my ~/.zshrc so code completion on files/dirs work as I need it.
I've found various ressources online on the zstyle completion syntax, and code example but some parts of it are still black magic to me.
So far, here is where I am, after some fiddling and testing :
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'm:{a-zA-Z}={A-Za-z} l:|=* r:|=*'
Here is what I understand from it :
zstyle ':completion:*' means we are going to define a config value for completion
matcher-list is the config we update, here it defines how zsh match files/dir to suggest
'm:{a-zA-Z}={A-Za-z}' 'm:{a-zA-Z}={A-Za-z} l:|=* r:|=*' are the arguments (values) to pass to the matcher-list config.
Here I have two arguments, separated by a space. It means zsh will first try to find files that match the first arg, and if it found nothing will try files that match the second arg
And here it becomes fuzzy :
I get that 'm:{a-zA-Z}={A-Za-z}' make the match case insensitive but I do not quite understand the syntax.
I also get that 'm:{a-zA-Z}={A-Za-z} l:|=* r:|=*' still is case insensitive, but also search for the match in the whole string, not just the start. But, I don't get the syntax either.
Can someone confirm my previous assertions, and correct them if needed, as well as detail the voodoo syntax of the arguments ?
Thanks
Edit : Oh, and sorry if the question is more fitted to superuser.com, I had a hard figuring which site was better suited for it.

Hi the zsh doc for this is at
http://zsh.sourceforge.net/Doc/Release/Completion-Widgets.html#Completion-Matching-Control

Related

bash array slicing strange syntax in perl path: `${PATH:+:${PATH}}"`

On Linux Ubuntu, when you do sudo apt update && sudo apt install perl, it adds the following to the bottom of your ~/.bashrc file (at least, many months later, I think that is what added those lines):
PATH="/home/gabriel/perl5/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="/home/gabriel/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="/home/gabriel/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"/home/gabriel/perl5\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=/home/gabriel/perl5"; export PERL_MM_OPT;
What does this strange syntax do in many of the lines, including in the first line? It appears to be some sort of bash array slicing:
${PATH:+:${PATH}}
The ${PATH} part is pretty straightforward: it reads the contents of the PATH variable, but the rest is pretty cryptic to me.
It's not array slicing; it's a use of one of the POSIX parameter expansion operators. From the bash man page, in the Parameter Expansions section,
${parameter:+word}
Use Alternate Value. If parameter is null or unset, nothing is
substituted, otherwise the expansion of word is substituted.
It's a complex way of making sure that you only add a : to the value if PATH isn't empty to start with. A longer, clearer way of writing it would be
if [ -n "$PATH" ]; then
PATH=/home/gabriel/perl5/bin:$PATH
else
PATH=/home/gabriel/perl5/bin
fi
However, since it if almost inconceivable that PATH is empty when .basrhc is sourced, it would be simpler to just prepend the new path and be done with it.
PATH=/home/gabriel/perl5/bin:$PATH
If PATH actually ended with a :, it would implicitly include the current working directory in the search path, which isn't a good idea for security reasons. Also from the bash man page, in the section on Shell Variables under the entry for PATH:
A zero-length (null) directory name in the
value of PATH indicates the current directory. A null directory
name may appear as two adjacent colons, or as an initial or
trailing colon.
As an aside, it's good to understand what various installers try to add to your shell configuration. It's not always necessary, and sometimes can actively change something you already have configure.
I would much prefer if packages simply printed instructions for what needs to be added to your configuration (and why), and leave it to the user to make the appropriate modifications.
What does this strange syntax do in many of the lines, including in the first line?
It's the ${parameter:+word} form of parameter expansion where word becomes the expanded value if parameter is not unset and not having the value of an empty string (a.k.a. null).

Are there any multiline comments in fish?

I am learning Fish and I want to know how to make multiline comments but the docs are hard to understand.
Any Help Apprecited.
You can pass a quoted string that spans multiple lines to a no-op command. This also works in other shells.
: '
This is a multi-line
comment.
'
The : command is an alias to true.
The documentation doesn't seem to include an explicit references for how to add a comment to a fish script. The implication via examples is that the only comment available is the POSIX-shell style rest-of-line comment that starts with an unquoted #.

Conditional Characters in Shell Globbing - Bash/Zsh

I'm trying to get a case statement to match one of four inputs in a Bash/Zsh shell:
v
-v
version
--version
I'm looking for this in the below case statement:
case "$1" in
?(--)version|?(-)v)
# do stuff
;;
esac
And I find that this isn't working. From what I've read, ?(pattern) is how to match 0 or one occurrences of a pattern.
I did have it working by matching the case
--version|version|-v|v)
But it would be nice to have something neater, plus, it's a learning experience!
I imagine this is probably down to me not escaping things properly, but I also tried encompassing my original string in double quotes ("), yet I got no output again.
Any advice?
To provide an answer to this, the best solution (if insisting on using some form of globbing) is:
(|--)version|(|-)v )
But as rightly pointed out by #kvantour, it's much better to just stick with the simpler:
--version|version|-v|v )

Way to create multiline comments in Bash?

I have recently started studying shell script and I'd like to be able to comment out a set of lines in a shell script. I mean like it is in case of C/Java :
/* comment1
comment2
comment3
*/`
How could I do that?
Use : ' to open and ' to close.
For example:
: '
This is a
very neat comment
in bash
'
Multiline comment in bash
: <<'END_COMMENT'
This is a heredoc (<<) redirected to a NOP command (:).
The single quotes around END_COMMENT are important,
because it disables variable resolving and command resolving
within these lines. Without the single-quotes around END_COMMENT,
the following two $() `` commands would get executed:
$(gibberish command)
`rm -fr mydir`
comment1
comment2
comment3
END_COMMENT
Note: I updated this answer based on comments and other answers, so comments prior to May 22nd 2020 may no longer apply. Also I noticed today that some IDE's like VS Code and PyCharm do not recognize a HEREDOC marker that contains spaces, whereas bash has no problem with it, so I'm updating this answer again.
Bash does not provide a builtin syntax for multi-line comment but there are hacks using existing bash syntax that "happen to work now".
Personally I think the simplest (ie least noisy, least weird, easiest to type, most explicit) is to use a quoted HEREDOC, but make it obvious what you are doing, and use the same HEREDOC marker everywhere:
<<'###BLOCK-COMMENT'
line 1
line 2
line 3
line 4
###BLOCK-COMMENT
Single-quoting the HEREDOC marker avoids some shell parsing side-effects, such as weird subsitutions that would cause crash or output, and even parsing of the marker itself. So the single-quotes give you more freedom on the open-close comment marker.
For example the following uses a triple hash which kind of suggests multi-line comment in bash. This would crash the script if the single quotes were absent. Even if you remove ###, the FOO{} would crash the script (or cause bad substitution to be printed if no set -e) if it weren't for the single quotes:
set -e
<<'###BLOCK-COMMENT'
something something ${FOO{}} something
more comment
###BLOCK-COMMENT
ls
You could of course just use
set -e
<<'###'
something something ${FOO{}} something
more comment
###
ls
but the intent of this is definitely less clear to a reader unfamiliar with this trickery.
Note my original answer used '### BLOCK COMMENT', which is fine if you use vanilla vi/vim but today I noticed that PyCharm and VS Code don't recognize the closing marker if it has spaces.
Nowadays any good editor allows you to press ctrl-/ or similar, to un/comment the selection. Everyone definitely understands this:
# something something ${FOO{}} something
# more comment
# yet another line of comment
although admittedly, this is not nearly as convenient as the block comment above if you want to re-fill your paragraphs.
There are surely other techniques, but there doesn't seem to be a "conventional" way to do it. It would be nice if ###> and ###< could be added to bash to indicate start and end of comment block, seems like it could be pretty straightforward.
After reading the other answers here I came up with the below, which IMHO makes it really clear it's a comment. Especially suitable for in-script usage info:
<< ////
Usage:
This script launches a spaceship to the moon. It's doing so by
leveraging the power of the Fifth Element, AKA Leeloo.
Will only work if you're Bruce Willis or a relative of Milla Jovovich.
////
As a programmer, the sequence of slashes immediately registers in my brain as a comment (even though slashes are normally used for line comments).
Of course, "////" is just a string; the number of slashes in the prefix and the suffix must be equal.
I tried the chosen answer, but found when I ran a shell script having it, the whole thing was getting printed to screen (similar to how jupyter notebooks print out everything in '''xx''' quotes) and there was an error message at end. It wasn't doing anything, but: scary. Then I realised while editing it that single-quotes can span multiple lines. So.. lets just assign the block to a variable.
x='
echo "these lines will all become comments."
echo "just make sure you don_t use single-quotes!"
ls -l
date
'
what's your opinion on this one?
function giveitauniquename()
{
so this is a comment
echo "there's no need to further escape apostrophes/etc if you are commenting your code this way"
the drawback is it will be stored in memory as a function as long as your script runs unless you explicitly unset it
only valid-ish bash allowed inside for instance these would not work without the "pound" signs:
1, for #((
2, this #wouldn't work either
function giveitadifferentuniquename()
{
echo nestable
}
}
Here's how I do multiline comments in bash.
This mechanism has two advantages that I appreciate. One is that comments can be nested. The other is that blocks can be enabled by simply commenting out the initiating line.
#!/bin/bash
# : <<'####.block.A'
echo "foo {" 1>&2
fn data1
echo "foo }" 1>&2
: <<'####.block.B'
fn data2 || exit
exit 1
####.block.B
echo "can't happen" 1>&2
####.block.A
In the example above the "B" block is commented out, but the parts of the "A" block that are not the "B" block are not commented out.
Running that example will produce this output:
foo {
./example: line 5: fn: command not found
foo }
can't happen
Simple solution, not much smart:
Temporarily block a part of a script:
if false; then
while you respect syntax a bit, please
do write here (almost) whatever you want.
but when you are
done # write
fi
A bit sophisticated version:
time_of_debug=false # Let's set this variable at the beginning of a script
if $time_of_debug; then # in a middle of the script
echo I keep this code aside until there is the time of debug!
fi
in plain bash
to comment out
a block of code
i do
:||{
block
of code
}

Bash: select a previous command that matches a pattern

I know about the bash history navigation with the Up and Down arrows.
I would like a lazy way to select a previous command that matches some regex (that is shorter than the whole command, so it takes less time to be typed).
Is it possible with bash?
If not, do other shells have such a feature?
You can always use CTRL-R to search your history backward, and type some part of the previous command. Hitting CTRL-R again (after the first hit) repeats your query (jumps to the next match if any).
Personally I use this for regex search (as regex searching is not possible yet (AFAIK)):
# search (using perl regexp syntax) your entire history
function histgrep()
{
grep -P $# ~/.bash_history
}
Edit:
For searching most recent history items via that function, see this (on setting $PROMPT_COMMAND ).
Zsolt, avoid hard-coded filenames, use the HISTFILE variable instead, with a fallback if you're really paranoid: ${HISTFILE:-~/.bash_history} ;-)
Any why grepping directly through the history file?! You'd lose the history number, which is necessary to replicate the command (e.g. !33 to execute again the 33th entry from your history) without having to copy&paste grep's output.
Please keep in mind that using that kind of $# expansions may fail at various (epic) levels. For instance, an argument beginning with "-" (histgrep -h) will usually hang, or shoot yourself in the foot. Indeed, this basic example may be worked around easily, following the classic "--" way of separating arguments from options, but the discussion has no ending, remembering that the arguments to be provided to that hack would be regular expressions. ;-)
Oh, and isn't histgrep somehow a too verbose choice? h, i, Tab, g, Tab :)
IMHO I'd stick to using ^R, falling back to history | grep ... whenever necessary.
Anyway, for the sake of the example, I'd (lazily) rewrite this little helper as:
function hgrep() { history | grep -P -- "$*"; }
See my answer here
Example:
$echo happy
happy
$!?pp?
happy
I prefere this solution since one can see all the contents of the whole history in addition to search by regex, and the regex-type is according to the very good regex engine of vim:
vim -u /root/.vimrc -M + <( history )
This I have given in this my post:
https://unix.stackexchange.com/questions/718828/search-in-whole-bash-history-list-with-full-featured-regex-engine
Regards
Anton Wessel

Resources