Home > OS >  Bash find or xargs evaluates variables and subshells only once
Bash find or xargs evaluates variables and subshells only once

Time:01-29

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

CodePudding user response:

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
    
  •  Tags:  
  • Related