3

Background

I have what I think should be a simple case. I want to find all files with "cisco" in the name and do something with those files (via xargs).

Finding files with ls

Before I use xargs, the first step is listing all relevant files. Listing files is easy with ls | grep cisco...

[mpenning@Bucksnort post]$ ls | grep cisco
cisco-asa-double-nat.rst
cisco-asa-packet-capture.rst
cisco-eem-tcl.rst
cisco-ip-sla-tracking.rst
cisco_autonomous_to_lwap.rst
[mpenning@Bucksnort post]$

Finding files with find

Although it's probably not required in this specific case, find is generally considered safer when piping into xargs. However, all logic seems to go out the window when I use find -regex.

[mpenning@Bucksnort post]$ find -regextype grep -regex ".*/cisco*" -print
[mpenning@Bucksnort post]$

However, I know I can find those files...

[mpenning@Bucksnort post]$ find | grep cisco
./cisco-eem-tcl.rst
./parsing-cisco-output-w-textfsm.rst
./cisco_autonomous_to_lwap.rst
./cisco-ip-sla-tracking.rst
./cisco-asa-double-nat.rst
./cisco-asa-packet-capture.rst
[mpenning@Bucksnort post]$

Questions

I realize that find -regex has to match the full path that is returned, but why isn't find -regextype grep -regex ".*/cisco*" -print working above? Shouldn't .*/cisco* match the path?


NOTES

I know I could just use find -path "*cisco*" to solve the problem, but the point of the question is to understand why my -regex usage is wrong.

  • Because the representation of ./ means "from the dir i´m in, to deeper", and it should not have a * between those characters. -regex "./*cisco*" should match the path, but not -regex ".*/cisco*" ; EDIT: There is a simmilar question here: stackoverflow.com/questions/19111067/… :) – user34720 Dec 18 '13 at 9:48
  • 3
    You probably want: .*/cisco.*. – otokan Dec 18 '13 at 9:55
  • 1
    As Herman and otokan have pointed out, the problem is you are using * wrongly at the end. It means something different in a regular expression than a glob. However, if you use it unquoted with grep (e.g. ls | grep cisco*), it passes through the shell first and is treated as a glob, not a regexp. This behavior has maybe led you to believe the wrong thing. – goldilocks Dec 18 '13 at 12:08
  • @goldilocks, after more than a decade of regex usage, you'd think I'd know that by now. Old shell globbing habits die hard :-) – Mike Pennington Dec 18 '13 at 12:51
6

Finding with ls: first things first, ls | grep cisco is a bit verbose, since cisco isn't a regular expression. Try:

ls *cisco*

Using find: along the same lines, -regex is overkill with a simple, static pattern. How about:

find -name '*cisco*'

The quotes are required so the glob is interpreted by find, not the shell. Also, -print is required for many versions of find, but is optional (and the default predicate) for others (e.g. GNU find). Feel free to add it if you need it.

If you need to search for ‘cisco’ in the full pathname, you could try this:

find -path '*cisco*'

which is equivalent to find | fgrep cisco.

Using find with regular expressions: let's do that anyway, since this is what you want. Shamelessly copying from the GNU find manpage:

-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'.

What this means is that your regular expression is wrapped in an invisible ^...$, so it must match every character in the full pathname of the file. So, as nwildner and otokan said in the comments, you should use something like:

find -regex '.*cisco.*'

And you don't even need the -regextype for something this simple.

  • 1
    About your first sentence. I hate to be pedantic, but cisco is a regular expression also, which matches the string cisco. It's just not a very fancy regex. – user13742 Dec 20 '13 at 16:31
2

The reason why find -regex ".*/cisco*" won't match any paths like ./cisco-eem-tcl.rst or ./cisco_autonomous_to_lwap.rst is as follows:

  • .* matches anything - any character, zero or more times
  • / matches a single slash - so far so good
  • cisco* matches cisc, followed by any number of o's.

Remember that the asterix * operator constrains the repetition of its preceding item which is o in this case. That means we have nothing matching this last component of the pattern, because it would be something like cisc, cisco, ciscoo, ciscooo ... ad infinitum.

If you need to express that anything can follow the word cisco, then use .* after that:

find -regex '.*/cisco.*'

Maybe you'd like to be a little more restrictive on your search results by matching only rst files:

find -regex '.*/cisco.*\.rst'

However, doing a simple match on the filename component of the path such as this doesn't require the expressive power of regexes so you can do just fine using only -name with a glob to get the same match:

find -name 'cisco*.rst'

and to process these matches with xargs you would do somthing like this:

find -name 'cisco*.rst' -print0 | xargs -0 command

You should always remember that regexes and globs are different things syntactically and semantically. See man find and read about the -name and -regex options to learn more.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.