zsh: parsing wildcards in variables used in arguments on zsh commands? - shell

ls *.txt shows all files whose name ends with .txt
However if I do the following on a zsh shell: (on macOS 10.15 Catalina in my case)
a=*.txt
b='*.txt'
c="*.txt"
# trying no quotes, single quotes, double quotes
# although doesn't make any difference here
ls $a
ls $b
ls $c
ls "$a"
ls "$b"
ls "$c"
I'm getting
ls: *.txt: No such file or directory
in all cases. How do I include wildcards in a variable and then have commands like ls actually process it as wildcards, rather than literal characters?

You should use ~(tilde) between $ and variable name
to perform globbing in zsh. That is
ls $~a

You can enable wildcards in zsh by using the command:
unsetopt nomatch
If you want to make the change permanent, put the command above into your .zshrc file.

Related

Glob pattern works in shell command but not in Perl backticks

I have 3 files in 3 different directories and I need to printout only files from DIR 1 and 2
1 /tmp/CDE/fileA.log
2 /tmp/CFGH/fileB.log
3 /tmp/CILM_NO/fileC.log
if I run from bash /bin/ls /tmp/C{[A-Z][A-Z],[A-Z][A-Z][A-Z]}/*.log it works and I get:
/tmp/CDE/fileA.log /tmp/CFGH/fileB.log
if I run ls bash command from script perl:
$cmd=`/bin/ls /tmp/C{[A-Z][A-Z],[A-Z][A-Z][A-Z]}/*.log`;
chomp($cmd);
print "$cmd\n";
I receive:
/bin/ls: cannot access /tmp/C{[A-Z][A-Z],[A-Z][A-Z][A-Z]}/*.log: No such file or directory
It looks I need to escape \{ or \, or \} but got the same output and it does not work
I also tried using quote instead of escaping but still got same error output
It's not a matter of permission, script is 777
Can't sort of it.
(not an answer, an explanation)
In your shell
ls /tmp/C{[A-Z][A-Z],[A-Z][A-Z][A-Z]}/*.log
That uses bash Brace Expansion. Bash will expand that to
ls /tmp/C[A-Z][A-Z]/*.log /tmp/C[A-Z][A-Z][A-Z]/*.log
And then do Filename Expansion
in perl
$cmd=`/bin/ls /tmp/C{[A-Z][A-Z],[A-Z][A-Z][A-Z]}/*.log`;
The backticks will call out to /bin/sh not bash, so the brace expansion will not happen

bash equivalent to tcsh modifiers for extracting path components

In tcsh I can extract second path element from the end of path by following way
cd /some/long/directory/structure/path/
set x=`pwd`
echo ${x:h:h:t}
directory
How can I do the same in bash?
I mean , does bash also have this kind of modifiers?
The csh-style modifiers can be used with history expansion (unsurprisingly, because history expansion was borrowed from csh).
$ cd /some/long/directory/structure/path/
$ echo !!:1:h:h:t
echo directory
directory
!!:1 selects word 1 (counting from zero) of the previous command, so the argument to cd.
(echo directory appears on standard error because the shell defaults to displaying the result of history expansion before actually executing the resulting command.)
In a non-interactive bash script, history expansion commands as in #chepner's answer won't normally be available. However, you do have parameter expansions like:
$ cd /some/long//directory///structure/path/
$ set x=$(pwd)
$ echo $x
/some/long/directory/structure/path
$ y=${y%/*/*} # each /* is equivalent to one :h
$ y=${y##*/} # equivalent to :t
$ echo $y
directory
cd /some/long/path/somewhere
x=$PWD
basename "$(dirname "$x")"
> path
dirname gets the absolute path of the parent folder of the argument. basename gets the name of the argument.
Edit: remembered the much better way than I was doing before.

Why doesn't zsh syntax work in this script?

I'm just trying to understand what is happening here, so that I understand how to parse strings in shell scripts better.
I know that usually, when you try to pass a string of arguments separated by spaces directly to a command, they will be treated as a single string argument and therefore not recognized:
>check="FileA.txt FileB.txt"
>ls $check
ls: cannot access FileA.txt FileB.txt: No such file or directory
However, in this script two arguments are taken each as space separated strings. In this case, both strings are recognizes as lists of arguments that can be passed to different commands:
testscript.sh
while getopts o:p: arguments
do
case $arguments in
o) olist="$OPTARG";;
p) plist=$OPTARG;;
esac
done
echo "olist"
ls -l $olist
echo "plist"
ls -l $plist
the output is then as follows:
>testscript.sh -o "fileA.txt fileB.txt" -p "file1.txt file2.txt"
Olist
fileA.txt
fileB.txt
plist
file1.txt
file2.txt
What is different here? Why are the space separated strings suddenly recognized as lists?
Your script does not start with a #!-line and does therefore not specify an interpreter. In that case the default is used, which is /bin/sh and not your login shell or the shell you are starting the script from (unless that is /bin/sh of course). Chances are good that /bin/sh is not a zsh, as most distributions and Unices seem to use sh, bash, dash or ksh as default shell. All of which handle parameter expansion such that strings are handles as lists if the parameter was not quoted with double-quotes.
If you want to use zsh as interpreter for your scripts, you have to specify it in the first line of the script:
#!/usr/bin/zsh
Modify the path to wherever your zsh resides.
You can also use env as a wrapper:
#!/usr/bin/env zsh
This makes you more independent of the actual location of zsh, it just has to be in $PATH.
As a matter of fact (using bash)...
sh$ check="FileA.txt FileB.txt"
sh$ ls $check
ls: cannot access FileA.txt: No such file or directory
ls: cannot access FileB.txt: No such file or directory
When you write $check without quotes, the variable is substituted by its content. Insides paces (or to be precises inside occurrences of IFS) are considered as field separators. Just as you where expecting it first.
The only way I know to reproduce your behavior is to set IFS to something else than its default value:
sh$ export IFS="-"
sh$ check="FileA.txt FileB.txt"
sh$ ls $check
ls: cannot access FileA.txt FileB.txt: No such file or directory

zsh completion inside quoted strings

Is it possible to configure zsh to suggest filenames (or anything else) inside of a quoted string?
I've seen this thread on bash: Bash TAB-completion inside double-quoted string
But I'm not sure whether that solution is compatible between shells.
No problem for tab completion inside quotes.
$ touch "spaces in a filename"
$ ls
spaces in a filename
$ ls sp[TAB]
gives ->
$ ls spaces\ in\ a\ filename
$ ls "sp[TAB]
gives ->
$ ls "spaces in a filename"

simple command execution inside while loop - bourne shell scripting

I have a file called inp.txt which lists 3 directory names
#!/bin/sh
while read dirname
do
echo $dirname
"ls -l" $dirname
done < inp.txt
When I run the above, I get this error:
line 5: ls -l: command not found
If I do just "ls" instead of "ls -l", it works fine. What am I missing here?
Get rid of the quotes.
while read dirname
do
echo "$dirname"
ls -l "$dirname"
done < inp.txt
When you have quotes you're saying, "treat this as a single word." The shell looks for an executable named ls -l rather than passing the argument -l to the command ls.
Nitpicker's note: If you want to use quotes properly, add them around "$dirname".
Otherwise, if you had a directory named "Music Files" with a space in the name, without quotes your script would treat that as two directory names and print something like:
ls: Music: No such file or directory
ls: Files: No such file or directory
The shell interprets space as argument separaters. By putting the quotes around something, you force shell to see it as one argument. When executing, the shell interprets the first argument as the command to execute. In this case you're telling it to execute the command named "ls -l" with no arguments, instead of "ls" with argument "-l".
You should remove the quotes around ls -l.

Resources