Repository: sharkdp/fd (language: rust)

Issue:
Improve the '--exec' help text
I see a lot of `fd` users that seem to think that they need to add `"{}"` to all commands. Some users also seem to think that the closing semicolon (which needs to be escaped) `\;` is required:
```
fd … -x command "{}" \;
```

In reality, 90% of the `--exec` use cases can be written without the `"{}"` part (which `fd` adds, if it is ommitted) because the file entry often comes last. The semicolon is never required if we teach users to put the `--exec` argument last.

I would like to improve several things in the documentation:
- [x] the README section ["Parallel command execution"](https://github.com/sharkdp/fd#parallel-command-execution) should be merged with the tutorial section.
- [x] We should put less focus on teaching the `{}`/`{.}`/`{/}`/… syntax and show more simple examples like `fd … -x wc -l` or `fd … -X vim` first.
- [x] The `--help` text and the man page should also be updated accordingly. I think two or three simple examples should also go inside the `--help` text and definitely into the man page.
- [x] Clarify that `-x` does not take a string argument (see below)


Discussion / hints:
Do you think the size of the explanations for `{}` and friends should be smaller? I quite like them as they are, tbh.

The examples should be the same for the man pages and the help output right?
> Do you think the size of the explanations for `{}` and friends should be smaller? I quite like them as they are, tbh.

I would not make the text on this less detailed. I just would like it to be a little bit less in the spotlight. It's not the first thing users should learn when reading about `--exec`. I think they should see a few easy examples (and most common use cases) first.

> The examples should be the same for the man pages and the help output right?

Yes 
the manpage definitely needs more attention regarding `-x`, it doesn't even mention the semicolon to end the command. I thought enclosing the command in quotes `"` would make sense as well?
> the manpage definitely needs more attention regarding `-x`, it doesn't even mention the semicolon to end the command.

:+1: 



> I thought enclosing the command in quotes `"` would make sense as well?

Hm, not necessarily. `fd … -x wc -l` is correct. `fd … -x "wc -l"` will *not* work. `fd … -x "wc" "-l"` is fine, but not really needed.
i was fooled by both the manpage and `--help`'s output, because they mention all options args are required to be before pattern and path (which none of your examples using `-x` adhere to), hence i assumed `-x` takes a string (as in: single argument, enclosed in `"`). would that have made parsing much easier, wouldn't it? Also more intuitive
> i was fooled by both the manpage and `--help`'s output, because they mention all options args are required to be before pattern and path

Hm, true. On the other hand, most "USAGE"/"SYNOPSIS" sections are written like this, even if the programs support flags/options after the positional arguments. Take `ls`, for example:
```
SYNOPSIS
       ls [OPTION]... [FILE]...
```
It still supports things like `ls FILE -l` though.

In fact, I think this is a quite essential feature for a command line interface. It allows users to easily repeat the last command while adding a new option (without having to move the cursor in front of the position arguments).

If you think we should improve the documentation in this respect, let us know.

> hence i assumed `-x` takes a string

I can definitely see why you would think that. Let's add this to the list of things that should be improved in this ticket.

> would that have made parsing much easier, wouldn't it? Also more intuitive

It would have made (*this* part of) parsing easier, yes. We still decided against this. There are several reasons:
* If we would have accepted a string like `"wc -l"` or something more complicated like `"echo -n \"found search result {}\", "` as an argument, we would have had two options: (1) use an intermediate shell to execute the command. this would have resulted in a drastic performance regression for fast-running commands. (2) we could have tried to parse the string ourselves, splitting it up into separate arguments in order to properly start the child process.
* Since file names can contain all sorts of characters (a space, a newline, a quote), we would need to know how to escape these characters when replacing patterns like `{}` in the command string (in both cases above!). I'm pretty sure that this is an impossible task in general. Just imagine someone wants to execute `convert '{}' "{/}.png"` while some search results contain a `'` character and some others contain a `"` character. By implementing `-x` the way we did it, we leave all of this to the users shell. Without any nasty surprises for the user.
* I like that I can type `fd … -x wc -l` instead of `fd … -x "wc -l"`.
> In fact, I think this is a quite essential feature for a command line interface. It allows users to easily repeat the last command while adding a new option (without having to move the cursor in front of the position arguments).

Totally agree. I've encountered programs that absolutely require options to come before arguments, and found it very annoying.
sure, but passing a string feels natural, no matter the position
Would you consider also adding an example of using `--exec` with a command that redirects its output to stdout and saves as a file? I personally just ran into this and it took me a little while to figure it out. So an example in the docs might be nice. I got my solution (in part) from [this SO question](https://stackoverflow.com/questions/15030563/redirecting-stdout-with-find-exec-and-without-creating-new-shell).

A somewhat trivial example of `cat`ing all text files below the current directory into a file with the same name in the directory above.
### via a script
`cat.sh`
```sh
#!/usr/bin/env sh
cat "$1" > "$2"
```
```sh
$ fd -e .txt --exec ./cat.sh {} ../{/} \; . .
```

### one-liner
```sh
$ fd -e .txt --exec sh -c "cat {} > ../{/}" \; . .
```

I appreciate there may be a better way of doing this that I haven't figured out.
Using an intermediate shell script is the best option here, I believe. Your first version is fine, but could be simplified to (this is what this ticket is about):
```
$ fd -e .txt --exec ./cat.sh {} ../{/}
```
The one-liner is not equivalent and will fail for more complex filenames with spaces, for example. Using `--exec sh -c "cat '{}' > ../'{/}'"` would be a bit safer, but can still fail for filenames involving quotes.
@Piraty 

> sure, but passing a string feels natural, no matter the position

Maybe, but I think I gave a couple of reasons why this is not a good idea. Note that `find` does the same. It also takes the command as a sequence of arguments instead of a string.
Windows 7.

``` console
$ fd -e srt -H -x cp {} D:\123   (works)

$ fd -e srt -H -x cp --parents {} D:\123   (does not work)
cp: cannot create regular file 'D:\123/Films\Casablanca\rus.srt': No such file or directory 

$ find -type f -name "*.srt" -exec cp --parents {} D:\123 ;   (works)
```

Fix patch:
diff --git a/doc/fd.1 b/doc/fd.1
index 78249877e..bff3b0e44 100644
--- a/doc/fd.1
+++ b/doc/fd.1
@@ -259,12 +259,22 @@ Provide paths to search as an alternative to the positional \fIpath\fR argument.
 \'fd [FLAGS/OPTIONS] \-\-search\-path PATH \-\-search\-path PATH2 [PATTERN]\'
 .TP
 .BI "\-x, \-\-exec " command
+.RS
 Execute
 .I command
-for each search result in parallel (use --threads=1 for sequential command execution). The following placeholders are substituted by a path derived from the current search result:
+for each search result in parallel (use --threads=1 for sequential command execution).
+
+Note that all subsequent positional arguments are considered to be arguments to the
+.I command
+- not to fd.
+It is therefore recommended to place the \-x/\-\-exec option last. Alternatively, you can supply
+a ';' argument to end the argument list and continue with more fd options.
+Most shells require ';' to be escaped: '\\;'.
+
+The following placeholders are substituted before the command is executed:
 .RS
 .IP {}
-path
+path (of the current search result)
 .IP {/}
 basename
 .IP {//}
@@ -274,15 +284,33 @@ path without file extension
 .IP {/.}
 basename without file extension
 .RE
+
+If no placeholder is present, an implicit "{}" at the end is assumed.
+
+Examples:
+
+  - find all *.zip files and unzip them:
+
+        fd -e zip -x unzip
+
+  - find *.h and *.cpp files and run "clang-format -i .." for each of them:
+
+        fd -e h -e cpp -x clang-format -i
+
+  - Convert all *.jpg files to *.png files:
+
+        fd -e jpg -x convert {} {.}.png
+.RE
 .TP
 .BI "\-X, \-\-exec-batch " command
+.RS
 Execute
 .I command
-with all search results at once.
-A single occurence of the following placeholders is authorized and substituted by the paths derived from the search results before the command is executed:
+once, with all search results as arguments.
+One of the following placeholders is substituted before the command is executed:
 .RS
 .IP {}
-path
+path (of all search results)
 .IP {/}
 basename
 .IP {//}
@@ -292,6 +320,22 @@ path without file extension
 .IP {/.}
 basename without file extension
 .RE
+
+If no placeholder is present, an implicit "{}" at the end is assumed.
+
+Examples:
+
+  - Find all test_*.py files and open them in your favorite editor:
+
+        fd -g 'test_*.py' -X vim
+
+    Note that this executes a single "vim" process with all search results as arguments.
+
+  - Find all *.rs files and count the lines with "wc -l ...":
+
+        fd -e rs -X wc -l
+.RE
+
 .SH PATTERN SYNTAX
 The regular expression syntax used by fd is documented here:
 
diff --git a/src/app.rs b/src/app.rs
index 5781daa85..2d6ec9484 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -298,16 +298,24 @@ pub fn build_app() -> App<'static, 'static> {
                 .conflicts_with("list-details")
                 .help("Execute a command for each search result")
                 .long_help(
-                    "Execute a command for each search result in parallel (use --threads=1 for sequential command execution).\n\
-                     All arguments following --exec are taken to be arguments to the command until the \
-                     argument ';' is encountered.\n\
-                     Each occurrence of the following placeholders is substituted by a path derived from the \
-                     current search result before the command is executed:\n  \
-                       '{}':   path\n  \
+                    "Execute a command for each search result in parallel (use --threads=1 for sequential command execution). \
+                     All positional arguments following --exec are considered to be arguments to the command - not to fd. \
+                     It is therefore recommended to place the '-x'/'--exec' option last.\n\
+                     The following placeholders are substituted before the command is executed:\n  \
+                       '{}':   path (of the current search result)\n  \
                        '{/}':  basename\n  \
                        '{//}': parent directory\n  \
                        '{.}':  path without file extension\n  \
-                       '{/.}': basename without file extension",
+                       '{/.}': basename without file extension\n\n\
+                     If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
+                     Examples:\n\n  \
+                       - find all *.zip files and unzip them:\n\n      \
+                           fd -e zip -x unzip\n\n  \
+                       - find *.h and *.cpp files and run \"clang-format -i ..\" for each of them:\n\n      \
+                           fd -e h -e cpp -x clang-format -i\n\n  \
+                       - Convert all *.jpg files to *.png files:\n\n      \
+                           fd -e jpg -x convert {} {.}.png\
+                    ",
                 ),
         )
         .arg(
@@ -321,16 +329,20 @@ pub fn build_app() -> App<'static, 'static> {
                 .conflicts_with_all(&["exec", "list-details"])
                 .help("Execute a command with all search results at once")
                 .long_help(
-                    "Execute a command with all search results at once.\n\
-                     All arguments following --exec-batch are taken to be arguments to the command until the \
-                     argument ';' is encountered.\n\
-                     A single occurrence of the following placeholders is authorized and substituted by the paths derived from the \
-                     search results before the command is executed:\n  \
-                       '{}':   path\n  \
+                    "Execute the given command once, with all search results as arguments.\n\
+                     One of the following placeholders is substituted before the command is executed:\n  \
+                       '{}':   path (of all search results)\n  \
                        '{/}':  basename\n  \
                        '{//}': parent directory\n  \
                        '{.}':  path without file extension\n  \
-                       '{/.}': basename without file extension",
+                       '{/.}': basename without file extension\n\n\
+                     If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
+                     Examples:\n\n  \
+                       - Find all test_*.py files and open them in your favorite editor:\n\n      \
+                           fd -g 'test_*.py' -X vim\n\n  \
+                       - Find all *.rs files and count the lines with \"wc -l ...\":\n\n      \
+                           fd -e rs -X wc -l\
+                     "
                 ),
         )
         .arg(


Test patch: