I'm using function like this.

$ find-grep () { find . -type f -name "$1" -print0 | xargs -0 grep "$2" ; }

After I type:

$ find-grep *.c foo

I want to get expanded last command string. In this case:

find . -type f -name "*.c" -print0 | xargs -0 grep "foo"

Is there way to do it easily?


A saner version of @slm's:

find-grep() {
  cmd=(find . -type f -name "$1" -exec grep "$2" {})
  printf '%q ' "${cmd[@]}"
  printf '+\n'
  "${cmd[@]}" +

(no need for pipes or xargs here) Or:

find-grep () (
  set -x
  find . -type f -name "$1" -exec grep "$2" {} +

(note the () instead of {} to start a subshell to limit the scope of set -x. Note that it does not cause more processes to be forked it's just that the fork for the process that will execute find is done earlier).

Remember you need to quote wildcard characters so they are not expanded by the shell:

find-grep '*.c' pattern

If instead, you want it to be pushed to the history, so that you see the expanded command when you press the Up key, you could write it:

find-grep() {
  cmd=$(printf '%q ' find . -type f -name "$1" -exec grep "$2" {})+
  history -s "$cmd"
  eval "$cmd"
  • Thanks for the more correct version. What's the advantage of the cmd=(...) vs. cmd=$"..." other than it's more readable? Also why can't the + be included to the array assignment? – slm Sep 21 '13 at 12:39
  • @slm, $"..." is for localisation, it doesn't make sense in this context. You need arrays to keep track of what the arguments are or use quoting. The + is not included to avoid the trailing space. – Stéphane Chazelas Sep 21 '13 at 12:47

Here's one way to do it:

$ find-grep () {
  cmd=(find . -type f -name \"$1\" -print0 \| xargs -0 grep \"$2\")
  printf "%s\n" "${cmd[*]}" 
  eval "${cmd[*]}"


Here's some sample data.

$ ls -l
total 8
-rw-rw-r-- 1 saml saml 4 Sep 21 10:44 1.c
-rw-rw-r-- 1 saml saml 4 Sep 21 10:44 2.c
-rw-rw-r-- 1 saml saml 0 Sep 21 10:22 3.c
-rw-rw-r-- 1 saml saml 0 Sep 21 10:22 4.c
-rw-rw-r-- 1 saml saml 0 Sep 21 10:22 5.c

Here's our function in action, 2 of the files contain the string "foo".

$ find-grep '*.c' "foo"
find . -type f -name "*.c" -print0 | xargs -0 grep "foo"


This particular version of your function does the following things differently.

  1. We define a variable with your command, $cmd=(find....). The command is wrapped into an array, (..).
  2. We echo the constructed array $cmd using printf. We pass the elements of the array using the * notation, ${cmd[*]}, so that we get the array as a single string, rather than as a discrete list of elements.
  3. Finally we evaluate the array $cmd, which runs it.

Another approach

You could also wrap the function like this: set -x ...function... set +x, which has the effect of turning on the debugging just for the duration of the ...function..., and then turning it off.

$ find-grep () { 
  set -x
  find . -type f -name "$1" -print0 | xargs -0 grep "$2"
  set +x


$ find-grep *.c foo
+ find . -type f -name '*.c' -print0
+ xargs -0 grep foo
+ set +x

This approach isn't as easy to read but it's showing you the same gross functions, just broken up based on how they were executed. First the find ..., followed by the xargs ....

  • Thanks slm! I'd use this way. Is it ok if I understand there is no easy way to expand string find-grep *.c foo to original command, if the function is not defined like this? – ironsand Sep 21 '13 at 2:58
  • @Tetsu - I provided an alternative method. I can't think of another way to do this. – slm Sep 21 '13 at 3:05
  • That won't work properly if arguments contain a few shell special characters which will be typically the case as it's meant to take patterns (which by the way the OP forgot to quote (*.c)) and regexps. – Stéphane Chazelas Sep 21 '13 at 8:38
  • @StephaneChazelas - tried to address some of your points, if you get a second please take a look and let me know if it does so, thanks! – slm Sep 21 '13 at 15:57
  • You don't need an array if you're passing a command line to eval. However, you need need to escape all the special characters in $1 and $2 (and you need to quote your variables as always). Putting double quotes around them wont's work. If $1 is '*"*' for instance or $2 is '', that won't be enough. Hence the printf %q` in the solution I gave. – Stéphane Chazelas Sep 21 '13 at 17:07

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.