8

Is there a way to see what the result of a find . -exec somecommand {} \; would be with substitutions, without actually running the commands? Like a dry run (or test run or print)?

For example, suppose I have the following file structure:

/a/1.txt
/a/2.txt
/a/b/3.txt

Is there a way to test find . type f -exec rm {} \; from within the a directory such that the output would printed to stdout but not executed such as:

rm 1.txt
rm 2.txt
rm b/3.txt

Update Note: rm is just an example command, I'm interested in the general case

7
  • Isn't this just basically the output of find, with your command prepended? Dec 27, 2020 at 16:08
  • 2
    In the most general case, this is not possible as the code executed by -exec may well affect the pathnames found by find. Thus, doing a "dry run" would potentially find different files from when running for real. Note that -exec acts like a test, just like -name, so if the -exec code returns false, the next stage is not executed for this pathname.
    – Kusalananda
    Dec 27, 2020 at 17:21
  • 2
    Does this answer your question? Preview the command formed by find -exec
    – muru
    Dec 28, 2020 at 2:46
  • @muru no it doesn't.
    – User
    Dec 28, 2020 at 5:04
  • @User ...it doesn't? How are the questions different? (If they're not the same, that means I've probably misunderstood this one). Dec 28, 2020 at 12:25

3 Answers 3

24

You can run echo rm instead of rm

find . type f -exec echo rm {} \;

Also, find has -delete option to delete files it finds

5
  • Minor point: beware of man echo since that will document /bin/echo, while in many cases simply running echo will use code embedded in the shell which might have subtle differences. Dec 27, 2020 at 11:38
  • 2
    However find will use /bin/echo as find is not part of the shell Dec 27, 2020 at 12:24
  • 1
    This is not an accurate printout of what the command would run. For example, if you have a file named two words.txt, echo doesn't let you distinguish whether it's one file named two and a second one words.txt Dec 27, 2020 at 17:34
  • 1
    The OP only asked to "see what the result [...] would be", not to need to be able to copy'n'paste or otherwise repeat it precisely, @CharlesDuffy Dec 27, 2020 at 18:05
  • @roaima, but if your result isn't precise, you aren't really seeing the result. You're seeing a different, corrupted result, and misinterpreting the result can mean being unaware of bugs in the code one is trying to test. Dec 27, 2020 at 18:47
3

For rm specifically, you don't need -exec: simply run find . -type f to list, and add -delete to delete the files listed by the previous command (obviously barring any matching files being created/deleted in the meantime).

Also, for commands like rm which take an arbitrary number of arguments you'll want to replace \; with + to run as few commands as possible.

3
  • 4
    You haven't actually answered the question here, which is asking about how to do a dry run. Dec 27, 2020 at 13:02
  • -delete is nonstandard and may not be supported by whatever version of find you are using.
    – chepner
    Dec 27, 2020 at 23:02
  • @djsmiley2kStaysInside This answer is specifically about the original case, removing files.
    – l0b0
    Dec 27, 2020 at 23:09
3

It's a bit of a mouthful, but unlike approaches using echo, the below outputs code you could run in your shell without any changes to have the correct result, even when your filenames contain quotes, spaces, shell metacharacters, etc.

printcmd() { printf '%q ' "$@"; printf '\n'; }

find . -exec bash -c "$(declare -f printcmd); "'printcmd "$@"' _ \
  somecommand {} \;

Note that the string we're prepending to our -exec argument is precisely bash -c "$(declare -f printcmd); "'printcmd "$@"' _ -- the $(declare -f printcmd) expands to the code for the function; after that, we actually call the function with arguments $1 and onward, and put _ as a placeholder for $0.

You can substitute zsh or ksh instead of bash, if you want output escaped for that shell.

5
  • 2
    With zsh: find . -exec zsh -c 'print -r "${(q)@}"' zsh somecommand {} + Dec 27, 2020 at 19:31
  • 2
    With recent versions of bash, see also: find . -exec bash -c 'echo -E "${@@Q}"' bash somecommand {} + (assuming a bash where the posix and xpg_echo options are not enabled by default) Dec 27, 2020 at 19:33
  • Since you're using Bash, export -f printcmd, and then find . -exec bash -c 'printcmd "$@"' _ somecommand {} \;
    – ilkkachu
    Dec 27, 2020 at 20:36
  • Actually, why bother with the function at all? Why not just find . -exec bash -c 'printf "%q " "$@"; echo' _ somecommand {} \; ?
    – ilkkachu
    Dec 27, 2020 at 20:37
  • @ilkkachu, the first why is to not break people who aren't on bash. With Apple making zsh default recently, there are rather a lot of those. The second "why" is that functions are more flexible so I prefer them in general; one could split the code over multiple lines, add more functionality, and use any kind of quoting one wants without extra hassle. Better to showcase a flexible practice than a brittle one. Dec 27, 2020 at 20:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .