Ad

Bash Find Or Xargs Evaluates Variables And Subshells Only Once

- 1 answer

I noticed that find ... -exec ... {} \; or xargs -i ... {} seems to evaluate variables or subshells (like $RANDOM or $(uuidgen)) only once, even the command was executed mutiple times.

For example:

$ find . -type f -name \*.txt -exec echo "$RANDOM {}" \;
28855 ./foo/bar.txt
28855 ./foo/bar1.txt
28855 ./foo/bar2.txt
28855 ./foo/bar3.txt
28855 ./foo/bar4.txt

$ grep -lr SOME_TEXT --include=\*.txt | xargs -i echo "$RANDOM {}"
6153 ./foo/bar.txt
6153 ./foo/bar1.txt
6153 ./foo/bar2.txt
6153 ./foo/bar3.txt
6153 ./foo/bar4.txt

Is there a way to get a result like below?

1543 ./foo/bar.txt
543 ./foo/bar1.txt
57224 ./foo/bar2.txt
3525 ./foo/bar3.txt
18952 ./foo/bar4.txt
Ad

Answer

Yes. The variable expansion is performed after the line has been accepted, but before it has been executed. This means that the command that ends up being executed is

'/usr/bin/find' '.' '-type' 'f' '-name' '*.txt' '-exec' 'echo' '28855 {}' ';'

Two basic ways around this:

  • Use another bash that will delay the execution:

    find . -type f -name \*.txt -exec bash -c 'echo "$RANDOM {}"' \;
    
  • Use a loop:

    for file in $(find . -type f -name \*.txt -print)
    do
      echo "$RANDOM $file"
    done
    
  • If your files have spaces, you have to do something different to preserve them:

    mapfile -d '' files < <(find . -type f -name \*.txt -print0)
    for file in "${files[@]}"
    do
      echo "$RANDOM $file"
    done
    
Ad
source: stackoverflow.com
Ad