Linux find command shell expansion - linux

I have just a little question I don't understand with the find command.
I can do this :
[root#hostnaoem# ❯❯❯ls /proc/*/fd
But this give me an error :
[root#hostnaoem# ❯❯❯ find /proc/*/fd -ls
find: `/proc/*/fd': No such file or directory
even if I use "/proc//fd", /proc/""/fd or "/proc/*/fd"
I've searched wha find shell expansion says about that, but I found nothing. Can someone tell me why?
Thanks

If you just RTFM, you'll learn that the syntax for find is:
find [-H] [-L] [-P] [-D debugopts] [-Olevel] [path...] [expression]
The usually used subset of that is:
find whereToSearch (-howToSearch arg)*
To find all files|directories named fd in /proc:
find /proc -name fd
-name is the most common howToSearch expression:
-name pattern
Base of file name (the path with the leading directories
removed) matches shell pattern pattern. The metacharacters
(`*', `?', and `[]') match a `.' at the start of the base name
(this is a change in findutils-4.2.2; see section STANDARDS CON‐
FORMANCE below). To ignore a directory and the files under it,
use -prune; see an example in the description of -path. Braces
are not recognised as being special, despite the fact that some
shells including Bash imbue braces with a special meaning in
shell patterns. The filename matching is performed with the use
of the fnmatch(3) library function. Don't forget to enclose
the pattern in quotes in order to protect it from expansion by
the shell.
(Note the the last sentence)
If your pattern contains slashes, you need -path or -wholename (same thing):
find /proc/ -wholename '/proc/[0-9]*/fd' 2>/dev/null
Other expressions you might want to use are:
-type
-depth, -mindepth, -maxdepth
-user, -uid
See find(1) to learn more about each search expressions. If you want to search the in-terminal manual (man find or man 1 find), you can use the / character to enter search mode (like Ctrl+F in most GUI apps).
Usage of ls with globbing (*) is generally a code smell. Unless you use the -d flag, it'll list the contents of the directories that match the glob pattern in addition to the matches.
I find the echo globpattern form generally more convenient for viewing the results of a glob pattern match.

This work :
[root#hostname # ❯❯❯ find /proc/ -path /proc/*/fd -ls
Regards.

Related

Shell script find command for matching file names with one or the other word

I am looking for a command for bash shell script to find the list of files in a directory whose names start with either WordA or WordB and end with digits.
I have this command with WordA and duplicating the same code with WordB.
find /log/ -name 'WordA*[[:digit:]]'
I tried putting Or condition in various formats such as (WordA|WordB), [WordA|WordB], [(WordA)|(WordB)], [(WordA)||(WordB)] to match WordA or WordB followed by digits.
None of them worked.
You need to use the -regextype supported in find command, in the case used the posix-extended type and do
-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.
So your command should be
find /log/ -regextype posix-extended -regex './(WordA|WordB)([[:digit:]]+)$'
In the find man page, under header Expressions, you'll find (no pun intended) the following
Operators
Operators join together the other items within the expression.
They include for example -o (meaning logical OR) and -a (meaning
logical AND). Where an operator is missing, -a is assumed.
Thus the answer to your problem appears to be
find /log/ -name 'WordA*[[:digit:]]' -o 'WordB*[[:digit:]]'

Difference between these two commands (w & w/out "") and why?

In linux, I have a file named test2 in my directory which I created using the touch command.
When I run the command
find . –name “*test*” -ls
It doesn't give me an error, but when I run
find . –name *test* -ls
It gave me an error
find: paths must precede expression: test2
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
Why is this?
*test* gets glob expanded by your shell (into more than one token).
Whereas no glob expansion happens in "*test*" because the surrounding " symbols prevent globbing.
Your shell is intercepting *test* and looking for files and directories in the current directory that match that expression, before it passes the expanded list to find. find expects only a single string in that spot, whereas the expanded list may be 0 or many strings.
With quotes, the shell ignores the asterisks and passes the raw string *test* to find, which then uses those asterisks as wildcards as you'd expect.

Finding files of fixed length

I am trying to find file names that start with the letter 'a' and are of length 6. I have tried many variations, the latest one being:
find /usr/bin -type f -regex "^[a]" > grep {6}
However I get an error message of:
find: paths must precede expression: {6}
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
What am I doing wrong?
Without any regexes, just globbing:
find /usr/bin -type f -name 'a?????'
References:
Findutils manual: Shell pattern matching
Bash manual, Filename expansion and pattern matching
I would use the following command which is using extended posix regexes:
find /usr/bin -type f -regextype posix-extended -regex '.*/a.{5}'
Let me explain the pattern from the end:
.{5} matches five arbitrary characters
a matches a literal a
the / matches the path delimiter right before the filename
.* is the path, in this case /usr/bin
Btw, a simple command which does not even require a special regex engine would be:
find /usr/bin -type f -regex '.*/a.....'
$ is the end of the filename
..... are five arbitrary characters
a is a literal a
.*/ is the preceding path
Another thing. While your regex is wrong and grep is not required at all, why do you get this strange error message?
You are using find ... > grep where I think you wanted to use find ... | grep. Note that > will redirect the output of the find command to a file. In this case a file named grep. If you want to redirect the output of the find command into the input of a grep command you need to use the pipe symbol find ... | grep.
A > filename redirection can appear anywhere in a command line, it does not necessarily have to be at the end. That' why {6} is interpreted as the last argument to find. Since this argument is not expected, find supposed that you accidentally passed a search path at the end, which is a common mistake. That's why the message.

How to find files with specific pattern in directory with specific number? Linux

I've got folders named folder1 all the way up to folder150 and maybe beyond.. but I only want to find the complete path to text files in some of the folders (for example folder1 to folder50).
I thought a command like the following might work, but it is incorrect.
find '/path/to/directory/folder{1..50}' -name '*.txt'
The solution doesn't have to use find, as long as it does the correct thing.
find /path/to/directory/folder{1..50} -name '*.txt' 2>/dev/null
Or only basename
find /path/to/directory/folder{1..50} -name '*.txt' -exec basename {} \; 2>/dev/null
Or basename without .txt
find /path/to/directory/folder{1..50} -name '*.txt' -exec basename {} .txt \; 2>/dev/null
V. Michel's answer directly solves your problem; to complement it with an explanation:
Bash's brace expansion is only applied to unquoted strings; your solution attempt uses a single-quoted string, whose contents are by definition interpreted as literals.
Contrast the following two statements:
# WRONG:
# {...} inside a single-quoted (or double-quoted) string: interpreted as *literal*.
echo 'folder{1..3}' # -> 'folder{1..3}'
# OK:
# Unquoted use of {...} -> *brace expansion* is applied.
echo 'folder'{1..3} # -> 'folder1 folder2 folder 3'
Note how only the brace expression is left unquoted in the 2nd example above, which demonstrates that you can selectively mix quoted and unquoted substrings in Bash.
It is worth noting that it is - and can only be - Bash that performs brace expansion here, and find only sees the resulting, literal paths.[1]
find only accepts literal paths as filename operands.
(Some of find's primaries (tests), such as -name and -path, do support globs (as demonstrated in the question), but not brace expansion; to ensure that such globs are passed through intact to find, without premature expansion by Bash, they must be quoted; e.g., -name '*.txt')
[1] After Bash performs brace expansion, globbing (pathname expansion) may occur in addition, as demonstrated in ehaymore's answer; folder(?,[1-4]?,50) is brace-expanded to tokens folder?, folder[1-4]?, and folder50, the first two of which are subject to globbing, due to containing pattern metacharacters (?, [...]). Whether globbing is involved or not, the target program ultimately only sees the resulting literal paths.
You can give multiple directories to the find command, each matching part of the pattern you're looking for. For example,
find /path/to/directory/folder{?,[1-4]?,50} -name '*.txt'
which expands to three patterns:
folder? (matches 0-9)
folder[1-4]? (matches 10-49)
folder50
The question mark is a single-character wildcard.

wild cards on find and ls

I'm trying to figure out the wild-cards to do file operations.
I have these files in a directory for testing purposes:
file_BSD.GIF file_linux.gif file_unix
See my ls command,
$ ls *{.GIF,.gif}
file_BSD.GIF file_linux.gif
Which is OK.
But "find" doesn't seem to work the same way:
$ find -name *{.GIF,.gif}
find: paths must precede expression: file_linux.gif
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
By the way, I've read that "-iname" should locate both the uppercase and lowercase files, but that doesn't seem to work either:
$find -iname *.gif
./file_linux.gif
(This should locate the .GIF file as well, right?).
find -name *{.GIF,.gif} is wrong.
This command is first expanded by the shell to find -name *.GIF *.gif
Then further expanded to :
find -name file_BSD.GIF file_linux.gif
# as you have only these files in directory
Now this -name file_BSD.GIF file_linux.gif is passed to find. And this is wrong as there is no switch like file_linux.gif that is accepted by find.
What you need is this command.
find -name '*.GIF' -or -name '*.gif'
Assuming you want to collect .gif files in a case insensitive manner, this find command becomes,
find -iname '*.gif'
Note the single quotes (') here. It means *.GIF should be sent to find as is without any shell expansion. And find will use this as pattern. This single quote is necessary unless you escape the shell meta-characters. In that case the command would look like
find -iname \*.gif
You are having trouble with the parameter -iname of find because you must quote the patterns you give to it.
So, you should do:
find -iname '*.gif'
This is stated in the manual:
"... Please note that you should quote patterns as a matter of course, otherwise the shell will expand any wildcard characters in them."
You should understand that (in contrast to Windows) the shell is expanding the *{.GIF,.gif} before passing it to the find program.
You can feel what the shell does by replacing the program with echo.
So you should quote the program argument, like
echo \-name '*{.GIF,.gif}'
so run
find -name '*.{GIF,gif}'
Maybe you want
find -name '*.gif' -o -name '*.GIF'
Please read the Advanced Bash Scripting Guide (and perhaps the execve(2) man page, to understand how the kernel run programs).

Resources