Find command : special characters do not work as expected [duplicate] - regex

This question already has answers here:
How to use regex with find command?
(9 answers)
Closed 5 months ago.
I am trying to search some files in a specific directory called Dico with find command e.g.
I want to find all files ending with g : find ./Dico -name "*g works but find ./Dico -name ".*g or find ./Dico -name ".*g$ do not. Can someone explain me why ?
Another example would be to find : all files that start with a number followed by exactly 5 characters (lowercase) : find ./Dico -name "\[0-9\]+[a-z]\{5\}" or find ./Dico -name "\d+[a-z]{5}". In that case +, \d+ and {n} do seem to do nothing.. I've tried both {5} and \{5\} (emacs syntax) but still the special characters seem to not work correctly.
I am on Ubuntu 20.04. Thank you in advance.

First of all, -name does not use regex.
-name pattern
Base of file name (the path with the leading directories removed) matches shell pattern pattern.
A matching shell pattern could look like this:
find /Dico -name '[0-9][a-z][a-z][a-z][a-z][a-z]'
To use regex, you need to instead use the -regex option (and -regextype to select the regex dialect you want).
-regex pattern
File name matches regular expression pattern. This is a match on the whole path, not a search.
I selected the regex dialect egrep:
find /Dico -regextype egrep -regex '.*/[0-9]+[a-z]{5}'
You can do find -regextype help (or just about any invalid dialect) to get a list of the supported regex dialects.

Related

Optional character in bash regex [duplicate]

suppose I have two files: ac and abc. I want to find a regex to match both files. Normally I would expect the following regex to work, but it never does:
find ./ -name ab?c
I have tried escaping or not the questionmark, this never seems to work. Normally in the regex documentations I have found; ? means: previous character repeated 0 or 1 times, but find doesn't seem to understand this.
I have tried this on two different find versions: GNU find version 4.2.31 and
find (GNU findutils) 4.6.0
PS: this works with *, but I specifically would like to match just one optional character.
find ./ -name a*c
gives
./ac
./abc
The expression passed to -name is not a regex, it is a glob expression. A (single) glob expression can't be used for your use case but you can use regular expressions using -regex:
find -regex '.*/ab?c'
Btw, the default regular expression language is Emacs Style as explained here : https://www.emacswiki.org/emacs/RegularExpression . You can change the regex language using -regextype.
To match the expression with only one optional character try using or option:
touch abc ac abbc
find . -name "a?c" -or -name "ac"
Gives you only: abc and ac names.
Generally you can build pretty complex find queries using or and and options =)
The find -name option uses a glob pattern, which is not the same as a regex. For globs, ? means any single character. If you want a character to be optional, you need to use two patterns:
find ./ -name abc -o -name ac
Other answers are good enough to have a solution but knowing find's -regex option matches on whole entry is essential. So you can't just do a partial match:
-regex 'ab?c'
You have to use one or two dot-stars:
-regex '.*ab?c.*'
Also without wildcards this would be possible using grep:
ls . | grep 'ab\?c'

linux find files with optional character in their name

suppose I have two files: ac and abc. I want to find a regex to match both files. Normally I would expect the following regex to work, but it never does:
find ./ -name ab?c
I have tried escaping or not the questionmark, this never seems to work. Normally in the regex documentations I have found; ? means: previous character repeated 0 or 1 times, but find doesn't seem to understand this.
I have tried this on two different find versions: GNU find version 4.2.31 and
find (GNU findutils) 4.6.0
PS: this works with *, but I specifically would like to match just one optional character.
find ./ -name a*c
gives
./ac
./abc
The expression passed to -name is not a regex, it is a glob expression. A (single) glob expression can't be used for your use case but you can use regular expressions using -regex:
find -regex '.*/ab?c'
Btw, the default regular expression language is Emacs Style as explained here : https://www.emacswiki.org/emacs/RegularExpression . You can change the regex language using -regextype.
To match the expression with only one optional character try using or option:
touch abc ac abbc
find . -name "a?c" -or -name "ac"
Gives you only: abc and ac names.
Generally you can build pretty complex find queries using or and and options =)
The find -name option uses a glob pattern, which is not the same as a regex. For globs, ? means any single character. If you want a character to be optional, you need to use two patterns:
find ./ -name abc -o -name ac
Other answers are good enough to have a solution but knowing find's -regex option matches on whole entry is essential. So you can't just do a partial match:
-regex 'ab?c'
You have to use one or two dot-stars:
-regex '.*ab?c.*'
Also without wildcards this would be possible using grep:
ls . | grep 'ab\?c'

finding directories with regex

I'm trying to find directories that begin with '6g' then a 2 letter state, and 4 digit number. an example directory would be /home/6gAL0533/
typically I copy to all directories starting with 6g using
"find 6g* -maxdepth 0 -type d"
but I am at a point now where I need to copy files to particular directories based on their 4 digit number (for instance 0300 - 0500) but I cant seem to get the find command to work for me. I think i need to use regex with the single character "." like "find -type d -regex '6g..0[3-5]...' " but that returns no results. I am probably using regex syntax incorrectly, but I haven't found much info on using regex to find directories. Any help would be appreciated. Thanks!
You don't need find for this at all, and you also don't need regular expressions; glob patterns are quite adequate (which means you can be compatible with POSIX find, rather than requiring a GNU-extended version):
shopt -s nullglob
dirs=( /home/6g[A-Za-z][A-Za-z]0[3-5][0-9][0-9][0-9] )
printf '%q\n' "${dirs[#]}" # print results
cp -- "${dirs[#]}" /to/destination # copy results somewhere else
...or...
dirs=( /home/6g??0[3-5]??? ) # use wildcards, as in your proposed regex
...or...
find /home -type d -maxdepth 1 -name '6g??0[3-5]???'
If you want to use find regexes (as Charles Duffy points out, there are a variety of simpler solutions), you need to remember that -regex looks for matches to the entire pathname.
As documented in man find:
-regex pattern
File name matches regular expression pattern. This is a match
on the whole path, not a search. For example, to match a file
named `./fubar3', you can use the regular expression `.*bar.' or
`.*b.*3', but not `f.*r3'. The regular expressions understood by
find are by default Emacs Regular Expressions, but this can be
changed with the -regextype option.
Consequently, you need:
find -type d -regex '.*/6g..0[3-5]...'

regex match either string in linux "find" command

I'm trying the following to recursively look for files ending in either .py or .py.server:
$ find -name "stub*.py(|\.server)"
However this does not work.
I've tried variations like:
$ find -name "stub*.(py|py\.server)"
They do not work either.
A simple find -name "*.py" does work so how come this regex does not?
Say:
find . \( -name "*.py" -o -name "*.py.server" \)
Saying so would result in file names matching *.py and *.py.server.
From man find:
expr1 -o expr2
Or; expr2 is not evaluated if expr1 is true.
EDIT: If you want to specify a regex, use the -regex option:
find . -type f -regex ".*\.\(py\|py\.server\)"
Find can take a regular expression pattern:
$ find . -regextype posix-extended -regex '.*[.]py([.]server)?$' -print
Options:
-regex pattern
File name matches regular expression pattern. This is a match on the whole path, not a search. For example, to match a file named ./fubar3, you can use the regular expression .*bar. or
.*b.*3, but not f.*r3. The regular expressions understood by find are by default Emacs
Regular Expressions, but this can be changed with the -regextype option.
-print True;
print the full file name on the standard output, followed by a newline. If you are piping
the output of find into another program and there is the faintest possibility that the files which
you are searching for might contain a newline, then you should seriously consider using the
-print0 option instead of -print. See the UNUSUAL FILENAMES section for information about how
unusual characters in filenames are handled.
-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.
A clearer description or the options. Don't forgot all the information can be found by reading man find or info find.
find -name does not use regexp, here's an extract from the man page on Ubuntu 12.04
-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.
So the pattern that -name takes is more like a shell glob and not at all like a regexp
If I wanted to find by regexp I'd do something like
find . -type f -print | egrep 'stub(\.py|\.server)'

repetition in GNU find regexp

I am trying to find all the files whose name contains exactly 14 digits (I'm trying to match a timestamp in the filename). I'm not sure how to get the GNU find regexp syntax for repetitions right.
I've tried find -regex ".*[0-9]{14} and find -regex ".*[0-9]\{14\}, neither of these turns up any results. Can you help me with the syntax?
remember, GNU find's -regex matches a whole path. Anyway, you can use a combination of find and grep to do the task, eg to find exactly 14 digits with no other characters
find . -type f -printf "%f\n" | grep -E "\b[0-9]{14}\b"
modify to suit your needs
Try changing the -regextype parameter to find.
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.
Strange, I just gave it a try and I could not get this work. Here's a workaround anyway (matching 2 consecutive numbers):
$ls
a123.txt a1b2c3.txt a45.txt b123.txt
$find -regex '.*[^0-9][0-9][0-9][^0-9].*'
./a45.txt