why is zsh globbing not working with find command? - unix

I have been using zsh globbing for commands such as:
vim **/filename
vim *.html.erb
and so on, but when I type in something like:
find . -name *mobile*
I get the response:
zsh: no matches found: *mobile*
Why?

find . -name *mobile* # does not work
vs
find . -name '*mobile*' # works
The difference is due to the steps that the shell takes when it parses a line. Normally, the shell expands any wildcards it finds before it runs the command. However, the single quotes mark the argument as being a literal, which means that the shell does not perform wildcard expansion on that argument before running the command.
To demonstrate the difference, suppose you are in a directory with the following files:
$ tree
./
mobile.1
dir/
mobile.2
In the first case, without single quotes, zsh will process as follows:
expand the glob, rendering simply mobile.1 (because that is the only matching filename in the current directory
pass the result to find, hence:
find . -name mobile.1
So find will only look for files named literally mobile.1
In the second form, with single quotes, the entire glob will be preserved and passed to find:
find . -name *mobile*
Which means that find will look for any filename containing the string "mobile".
The important thing to note here is that both zsh and find support the same wildcard syntax; by using single quotes, you induce find to handle the wildcards in this case rather than zsh.

Turns out that all you have to do to solve the problem is add some quotes around the input:
find . -name '*mobile*'
I don't really have an answer as to why just yet...and the documentation doesn't have an something that sticks out to me, but let me know if you know the answer!

For archival purposes, here is my substantial edit/reformatting of #Swiss's response above. The edit queue has been full every time I tried to edit, for hours, so I want to preserve this for future reference. I hope it's deemed to be constructive.
To be super-clear it's a revision of another person's work.
find . -name *mobile* # does not work
vs
find . -name '*mobile*' # works
The difference is due to the steps that the shell takes when it parses a line. Normally, the shell expands any wildcards it finds before it runs the command. However, single quotes marks the argument as being a literal, which means that the shell does not preform wildcard expansion on that argument before running the command.
To demonstrate the difference, suppose you are in a directory with the following files:
$ tree
./
mobile.1
dir/
mobile.2
In the first case, without single quotes, zsh will process as follows:
expand the glob, rendering simply mobile.1 (because that is the only matching filename in the current directory
pass the result to find, hence:
find . -name mobile.1
So find will only look for files named literally mobile.1
In the second form, with single quotes, the entire glob will be preserved and passed to find:
find . -name *mobile*
Which means that find will look for any filename containing the string "mobile".
The important thing to note here is that both zsh and find support the same wildcard syntax; by using single quotes, you induce find to handle the wildcards in this case rather than zsh.

Related

Passing wildcards to find

I'm trying to automate my test runner better. For that I need the update file name in a variable. As this name depends on a the version I'm trying to use a find with a pattern to get the file name. That works just fine in bash.
However if I use that same pattern in expect find complains that it can't find anything.
My guess is that expect is doing something to my wildcards. However my experiments with {}, "", '' or \ didn't result in it working.
I guess I could create a helper sh script to write it into a file and then read that file but I don't like that solution and there has to be an option to pass characters with special tcl meaning as arguments.
At the moment my call looks something like this with an absolute path in front of the pattern:
set pattern {[0-9]*/*test*}
set updateFile [exec find ${pattern} -type f]
The result is that find reports '[0-9]*/*test*': No such file or directory. The pattern is what I would expect and when I call find [0-9]*/*test* -type f in bash it results in the expected file path. Find also works fine as long as I don't have any wild cards.
Has anybody an idea what is wrong?
When you run find [0-9]*/*test* -type f in Bash, it's Bash who interprets the wildcard [0-9]*/*test* and expand it to multiple files. And then Bash would pass the expanded multiple files to find. That's to say find never sees the wildcard.
For exec find $pattern -type f, Tcl would not interpret what's in $pattern and pass it directly to find. Unfortunately find also does not interpret the wildcards here so it fails with error like find : '[0-9]*/*test*': No such file or directory.
To work around, you can invoke find with bash -c:
exec bash -c "find $pattern -type f"

File pattern to use for fetching files from FTP Server in Unix

I've an issue with file name pattern to be provided in order to fetch the pattern file from the FTP server.
Currently, I am using ABC_YYYYMMDD*.sha1 as the pattern to fetch files. It fetches the last file using the above pattern. Sometimes, .gz.sha1 comes later and sometimes, the other one.
ABC_20160801060000.sha1
ABC_20160801060000.txt.gz.sha1
I would need to provide the file name pattern in such a way that the file should always pick ABC_YYYYMMDDHHMISS.sha1 only.
Need a fool proof pattern which matches the required one only?
You can use find -regex option for matching these file-names:-
find . -type f -regextype posix-extended -regex '.*ABC_20[0-9]{2}(0[1-9]|1[0-2])([0-2][0-9]|3[0-1])([0-2][0-3])([0-5][0-9])([0-5][0-9])\.sha1'
Am using the -regex flag supported by find for this over the -name flag which does simple glob pattern matching.
The man page of find says below for the -regex:-
-regex pattern
File name matches regular expression pattern. This is a match
on the whole path, not a search.
-regextype name
This option controls the variety of regular expression syntax
understood by the ‘-regex’ and ‘-iregex’ tests. This option is
positional; that is, it only affects regular expressions which
occur later in the command line. If this option is not given, GNU
Emacs regular expressions are assumed.
More about posix-extended regex type at this page. Other supported regex-types can be found here.
To see it in action:-
$ ls ABC_2016*
ABC_20161231225950.sha1 ABC_20169231225990.sha1
$ find . -type f -regextype posix-extended -regex '.*ABC_20[0-9]{2}(0[1-9]|1[0-2])([0-2][0-9]|3[0-1])([0-2][0-3])([0-5][0-9])([0-5][0-9])\.sha1'
./ABC_20161231225950.sha1
Update:-
If the regextype is not supported in the find version, a simple glob construct using the -name flag can be used to achieve the same.
$ ls ABC_2016*
ABC_20161231225950.sha1 ABC_20169231225990.sha1
$ find . -type f -name 'ABC_2[0-9][0-9][0-9][0-1][0-2][0-3][0-9][0-2][0-2][0-5][0-9][0-5][0-9].sha1'
./ABC_20161231225950.sha1

"find" command returning nothing when searching through absolute path

Thought there might be a simple solution to this, but I can't seem to find it anywhere. It's a simple-enough problem. Say I have the following folder/file structure:
/home/
text1.txt
/mydir/
text2.txt
Then I input the command:
find . -name *.txt
This command returns "text1.txt" when called from within /home, and returns "text2.txt" when called from within /home/mydir, as it should.
However, when calling the following from /home...:
find /home/mydir -name *.txt
it returns nothing. My expectation is that it would return "text2.txt." Any thoughts? I have already checked to see if I have any wayward aliases assigned to find, and I have nothing.
It is also worth it to note that I have two unix machines. The use of an absolute path for "find" works on one machine and not the other. Can't go into much more detail than that, I'm afraid. Just looking for a direction to investigate this more.
Thanks to anyone who can help :-)
You should use
find . -name "*.txt"
otherwise bash will extract *.txt to text1.txt resulting in the following command:
find . -name text1.txt
And it will no longer match text2.txt

find specific file extension with find command

I would like to find any files within a given root that contain arbitrary extensions in the filename.
I saw this post:
How to delete all files with certain suffix before file extension
Based on that information, I tried this:
find . -iregex ".*\.\(wav\|aif\|wave\|aiff\)"
This seems like it should work, but I don't get any results printed to the terminal window.
Can anyone offer advice? I'm on OSX 10.7
Thanks,
jml
You are looking for:
find . -regex ".*\.\(wav\|aif\|wave\|aiff\)"
You were missing escape, \, characters on the or, |, operators
Is that an emacs style regex?
If not, try using -regextype. From the find man page on Linux (archaic):
-regextype type
Changes the regular expression syntax understood by -regex and -iregex tests which occur later on the command line. Currently-implemented types are emacs (this is the default), posix-awk, posix-basic, posix-egrep and posix-extended.
On MacOS X, the manual page for find says:
-iregex pattern
Like -regex, but the match is case insensitive.
-regex pattern
True if the whole path of the file matches pattern using regular expression. To match a file named './foo/xyzzy', you can use the regular expression '.*/[xyz]*' or '.*/foo/.*', but not 'xyzzy' or '/foo/'.
Some experimentation shows that:
find pdf -iregex ".*/.*.pdf"
finds a whole lot of PDF files in my folder full of them, but none of these variants find anything:
find pdf -iregex ".*/.*\.(pdf|doc|docx)"
find pdf -iregex ".*/.*\.\(pdf|doc|docx\)"
find pdf -iregex ".*/.*.(pdf|doc|docx)"
find pdf -iregex ".*/.*.\(pdf|doc|docx\)"
Consequently, one is forced to assume that the regexes supported by MacOS X (BSD) find do not include alternation (parentheses and pipes) amongst the recognized characters. 'Tis a pity: man 7 re_format implies it might, but it doesn't. The -regextype option is not supported on MacOS X (BSD), it seems.
So, it may be simplest to install GNU find, or to do N separate searches for the N different file extensions, or do one search for files in general and use egrep '\.(aff|wave?|aiff)$' to catch the files you're interested in. That rather assumes you don't use newlines in file names (spaces etc are OK, but newlines are not).

How to make the glob() function also match hidden dot files in Vim?

In a Linux or Mac environment, Vim’s glob() function doesn’t match dot files such as .vimrc or .hiddenfile. Is there a way to get it to match all files including hidden ones?
The command I’m using:
let s:BackupFiles = glob("~/.vimbackup/*")
I’ve even tried setting the mysterious {flag} parameter to 1, and yet it still doesn’t return the hidden files.
Update: Thanks ib! Here’s the result of what I’ve been working on: delete-old-backups.vim.
That is due to how the glob() function works: A single-star pattern
does not match hidden files by design. In most shells, the default
globbing style can be changed to do so (e.g., via shopt -s dotglob
in Bash), but it is not possible in Vim, unfortunately.
However, one has several possibilities to solve the problem still.
First and most obvious is to glob hidden and not hidden files
separately and then concatenate the results:
:let backupfiles = glob(&backupdir..'/*').."\n"..glob(&backupdir..'/.[^.]*')
(Be careful not to fetch the . and .. entries along with hidden files.)
Another, perhaps more convenient but less portable way is to use
the backtick expansion within the glob() call:
:let backupfiles = glob('`find '..&backupdir..' -maxdepth 1 -type f`')
This forces Vim to execute the command inside backticks to obtain
the list of files. The find shell command lists all files (-type f)
including the hidden ones, in the specified directory (-maxdepth 1
forbids recursion).

Resources