how to properly expand filenames in zsh alias - zsh

Wrote a seemingly simple alias to convert mp3 to wav but doesn't expand the files at run time. Changed it to a function after I was unable to get it working.
Was hoping to get an explanation of why it didn't work as written.
alias 2wav="for fn in *.mp3;do echo \"Converting $fn\";avconv -y -i $fn ${$(basename $fn .mp3)}.wav 2>/dev/null;done"

Do any of your filenames contain spaces? e.g. foo bar.mp3 would produce a command line of
avconv -y -i foo bar.mp3 foo bar.wav
^^^---input file, doesn't exist
^^^^^^^--output file, but since there's no input, it's useless
^^^^^^^^^^--- miscellaneous unknown/invalid arguments
You'd need quotes around the arguments:
alias ........ -y -i "$fn" "${$(basename .....}".wav

Solved (at least partially).
alias 2wav='for fn in *.mp3;do echo "Converting ${fn}";avconv -y -i ${fn} ${$(basename ${fn} .mp3)}.wav 2>/dev/null;done'
Not sure why this fixed it but I enclosed the command with single quotes and added braces around the vars and it just worked. True that spaces in file names would still be a potential problem. I'll have to experiment with that.

Solved.
alias 2wav='for fn in *.mp3;do echo "Converting ${fn}";avconv -y -i "${fn}" "${$(basename "${fn}" .mp3)}".wav 2>/dev/null;done'
Now works for filenames containing spaces. I'm guessing that the single quotes prevent the expansion from happening until runtime and that's why this works.
Any clarification is welcome...

Related

ZSH auto_vim (like auto_cd)

zsh has a feature (auto_cd) where just typing the directory name will automatically go to (cd) that directory. I'm curious if there would be a way to configure zsh to do something similar with file names, automatically open files with vim if I type only a file name?
There are three possibilities I can think of. First is suffix aliases which may automatically translate
% *.ps
to
% screen -d -m okular *.ps
after you do
alias -s ps='screen -d -m okular'
. But you need to define this alias for every file suffix. It is also processed before most expansions so if
% *.p?
matches same files as *.ps it won’t open anything.
Second is command_not_found handler:
function command_not_found_handler()
{
emulate -L zsh
for file in $# ; do test -e $file && xdg-open $file:A ; done
}
. But this does not work for absolute or relative paths, only for something that does not contain forward slashes.
Third is a hack overriding accept-line widget:
function xdg-open()
{
emulate -L zsh
for arg in $# ; do
command xdg-open $arg
endfor
}
function _-accept-line()
{
emulate -L zsh
FILE="${(z)BUFFER[1]}"
whence $FILE &>/dev/null || BUFFER="xdg-open $BUFFER"
zle .accept-line
}
zle -N accept-line _-accept-line
. The above alters the history (I can show how to avoid this) and is rather hackish. Good it does not disable suffix aliases (whence '*.ps' returns the value of the alias), I used to think it does. It does disable autocd though. I can avoid this (just || test -d $FILE after whence test), but who knows how many other things are getting corrupt as well. If you are fine with the first and second solutions better to use them.
I guess you can use "fasd_cd" which has an alias v which uses viminfo file to identifi files which you have opened at least once. In my environment it works like a charm.
Fast cd has other amazing stuff you will love!
Don't forget to set this alias on vim to open the last edited file:
alias lvim="vim -c \"normal '0\""

case insensitive glob listing in zsh

I have the following code:
$ print -l backgrounds/**/*.((#i)jpg|jpeg|gif|webp|png|svg|xcf|cur|ppm|pcd)
the intention was to list some image file indifernet of the case of file termination.
But my code seems to not be functional because won't list files whit uppercase endings.
Can someone explain my error in the above code example?
Thanks in advance.
You need the #i to apply to everything, not just jpg. You can use:
$ print -l backgrounds/**/*.(#i)(jpg|jpeg|gif|webp|png|svg|xcf|cur|ppm|pcd)
Make sure you have also done:
set -o extended_glob
Note that using #i requires that EXTENDED_GLOB be set in your script/shell:
setopt EXTENDED_GLOB
See the docs, section 1.8.4 Globbing Flags, or type man zshexpn.
And you want: *.(#i)(jpg|gif|etc)

How to edit path variable in ZSH

In my .bash_profile I have the following lines:
PATHDIRS="
/usr/local/mysql/bin
/usr/local/share/python
/opt/local/bin
/opt/local/sbin
$HOME/bin"
for dir in $PATHDIRS
do
if [ -d $dir ]; then
export PATH=$PATH:$dir
fi
done
However I tried copying this to my .zshrc, and the $PATH is not being set.
First I put echo statements inside the "if directory exists" function and I found that the if statement was evaluating to false, even for directories that clearly existed.
Then I removed the directory-exists check, and the $PATH was being set incorrectly like this:
/usr/bin:/bin:/usr/sbin:/sbin:
/usr/local/bin
/opt/local/bin
/opt/local/sbin
/Volumes/Xshare/kburke/bin
/usr/local/Cellar/ruby/1.9.2-p290/bin
/Users/kevin/.gem/ruby/1.8/bin
/Users/kevin/bin
None of the programs in the bottom directories were being found or executed.
What am I doing wrong?
Unlike other shells, zsh does not perform word splitting or globbing after variable substitution. Thus $PATHDIRS expands to a single string containing exactly the value of the variable, and not to a list of strings containing each separate whitespace-delimited piece of the value.
Using an array is the best way to express this (not only in zsh, but also in ksh and bash).
pathdirs=(
/usr/local/mysql/bin
…
~/bin
)
for dir in $pathdirs; do
if [ -d $dir ]; then
path+=$dir
fi
done
Since you probably aren't going to refer to pathdirs later, you might as well write it inline:
for dir in \
/usr/local/mysql/bin \
… \
~/bin
; do
if [[ -d $dir ]]; then path+=$dir; fi
done
There's even a shorter way to express this: add all the directories you like to the path array, then select the ones that exist.
path+=/usr/local/mysql/bin
…
path=($^path(N))
The N glob qualifier selects only the matches that exist. Add the -/ to the qualifier list (i.e. (-/N) or (N-/)) if you're worried that one of the elements may be something other than a directory or a symbolic link to one (e.g. a broken symlink). The ^ parameter expansion flag ensures that the glob qualifier applies to each array element separately.
You can also use the N qualifier to add an element only if it exists. Note that you need globbing to happen, so path+=/usr/local/mysql/bin(N) wouldn't work.
path+=(/usr/local/bin/mysql/bin(N-/))
You can put
setopt shwordsplit
in your .zshrc. Then zsh will perform world splitting like all Bourne shells do. That the default appears to be noshwordsplit is a misfeature that causes many a head scratching. I'd be surprised if it wasn't a FAQ. Lets see... yup:
http://zsh.sourceforge.net/FAQ/zshfaq03.html#l18
3.1: Why does $var where var="foo bar" not do what I expect?
Still not sure what the problem was (maybe newlines in $PATHDIRS)? but changing to zsh array syntax fixed it:
PATHDIRS=(
/usr/local/mysql/bin
/usr/local/share/python
/usr/local/scala/scala-2.8.0.final/bin
/opt/local/Library/Frameworks/Python.framework/Versions/2.6/bin
/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin
/opt/local/etc
/opt/local/bin
/opt/local/sbin
$HOME/.gem/ruby/1.8/bin
$HOME/bin)
and
path=($path $dir)

Get the `pwd` in an `alias`?

Is there a way I can get the pwd in an alias in my .zshrc file? I'm trying to do something like the following:
alias cleanup="rm -Rf `pwd`/{foo,bar,baz}"
This worked fine in bash; pwd is always the directory I've cd'd into, however in zsh it seems that it's evaluated when the .zshrc file is first loaded and always stays as my home directory. I've tested using with a really simple alias setup, but it never changes.
How can I have this change, so that calling the alias from a subdirectory always evaluates as that subdir?
EDIT: not sure if this will help, but I'm using zsh via oh-my-zsh on the mac.
When your .zshrc is loaded, the alias command is evaluated. The command consists of two words: a command name (the builtin alias), and one argument, which is the result of expanding cleanup="rm -Rf `pwd`/{foo,bar,baz}". Since backquotes are interpolated between double quotes, this argument expands to cleanup=rm -Rf /home/unpluggd/{foo,bar,baz} (that's a single shell word) where /home/unpluggd is the current directory at that time.
If you want to avoid interpolation at the time the command is defined, use single quotes instead. This is almost always what you want for aliases.
alias cleanup='rm -Rf `pwd`/{foo,bar,baz}'
However this is needlessly complicated. You don't need `pwd/` in front of file names! Just write
alias cleanup='rm -Rf -- {foo,bar,baz}'
(the -- is needed if foo might begin with a -, to avoid its being parsed as an option to rm), which can be simplified since the braces are no longer needed:
alias cleanup='rm -Rf -- foo bar baz'

How can I grep for a string that begins with a dash/hyphen?

I want to grep for the string that starts with a dash/hyphen, like -X, in a file, but it's confusing this as a command line argument.
I've tried:
grep "-X"
grep \-X
grep '-X'
Use:
grep -- -X
Documentation
Related: What does a bare double dash mean? (thanks to nutty about natty).
The dash is a special character in Bash as noted at http://tldp.org/LDP/abs/html/special-chars.html#DASHREF. So escaping this once just gets you past Bash, but Grep still has it's own meaning to dashes (by providing options).
So you really need to escape it twice (if you prefer not to use the other mentioned answers). The following will/should work
grep \\-X
grep '\-X'
grep "\-X"
One way to try out how Bash passes arguments to a script/program is to create a .sh script that just echos all the arguments. I use a script called echo-args.sh to play with from time to time, all it contains is:
echo $*
I invoke it as:
bash echo-args.sh \-X
bash echo-args.sh \\-X
bash echo-args.sh "\-X"
You get the idea.
grep -e -X will do the trick.
grep -- -X
grep \\-X
grep '\-X'
grep "\-X"
grep -e -X
grep [-]X
I dont have access to a Solaris machine, but grep "\-X" works for me on linux.
The correct way would be to use "--" to stop processing arguments, as already mentioned. This is due to the usage of getopt_long (GNU C-function from getopt.h) in the source of the tool.
This is why you notice the same phenomena on other command-line tools; since most of them are GNU tools, and use this call,they exhibit the same behavior.
As a side note - getopt_long is what gives us the cool choice between -rlo and --really_long_option and the combination of arguments in the interpreter.
If you're using another utility that passes a single argument to grep, you can use:
'[-]X'
you can use nawk
$ nawk '/-X/{print}' file
None of the answers not helped me (ubuntu 20.04 LTS).
I found a bit another option:
My case:
systemctl --help | grep -w -- --user
-w will match a whole word.
-- means end of command arguments (to mark -w as not part of the grep command)
ls -l | grep "^-"
Hope this one would serve your purpose.
grep "^-X" file
It will grep and pick all the lines form the file.
^ in the grep"^" indicates a line starting with

Resources