2

I'm using FreeBSD and having difficulty with commands that include {} as part of a string. For example, if renaming the found files. Note that this is an example of the situation. The question itself is about working around the issues of "{}" syntax in general:

find . -type f -name 'data*' -execdir mv {} OLD_{} \;
find . -type f -name 'data*' -execdir mv {} archive/{} \;

Note that -execdir avoids issues with the containing dir path by executing the command from within the containing dir and expanding {} to just the filename.

There are two problems:

  1. How to correctly quote the args in the -execdir mv clause (many files will have spaces or single quotes in their names).
  2. How to get the target to substitute the filename at all.

The second problem arises because {} for "found item's path" only seems to be expanded if it's surrounded by leading/trailing spaces, which messes up the command args. Example with non-space leading and trailing characters:

# /usr/bin/find . -maxdepth 1 -execdir echo "(result):" {}  \;
(result): .
(result): dir 1
(result): dir 2
(result): dir 3

# /usr/bin/find . -maxdepth 1 -execdir echo "(result):"{}  \;
(result):
(result):
(result):
(result):

# /usr/bin/find . -maxdepth 1 -execdir echo {}":(result)" \;
:(result)
:(result)
:(result)
:(result)

man find states that " Historic implementations of the -exec and -ok primaries did not replace the string “{}” in the utility name or the utility arguments if it had preceding or following non-whitespace characters. This version replaces it no matter where in the utility name or arguments it appears", but that doesn't seem to be happening.

How can I execute the command I want to run?

3
  • Related: unix.stackexchange.com/questions/358613/….
    – slm
    Commented Jul 3, 2018 at 3:41
  • 2
    The purported duplicate does not address either of the questions actually asked, not explaining whether and how to quote nor explaining the discrepancy with the manual. It answers the question not asked here and which this question actually assumes as a premise.
    – JdeBP
    Commented Jul 3, 2018 at 6:14
  • 1
    I'm a bit confused about the last two examples: if find doesn't replace the {}, then surely it should at least leave it as-is? What version of FreeBSD are you running? And what shell?
    – ilkkachu
    Commented Jul 4, 2018 at 19:21

3 Answers 3

2

This behaviour is not reproducible with find in FreeBSD 11.1-RELEASE when using /bin/sh as the shell. I was able to reproduce it under both /bin/csh and /bin/tcsh though.

To correct for this under csh and tcsh, quote {} as \{\} or as '{}', or use the below method.


To correctly concatenate the current pathname with some other string in an implementation of find that does not expand {} properly when using it as part of a string, one can do this with a child shell.

Example:

find . -type f -name '*.c' -exec sh -c 'printf "(result):%s\n" "$@"' sh {} +

or, with echo (but see "Why is printf better than echo?"),

find . -type f -name '*.c' -exec sh -c '
    for name do
        echo "(result):$name"
    done' sh {} +

or

find . -type f -name '*.c' -execdir sh -c '
    for name do
        mv -- "$name" "OLD_${name##*/}"
    done' sh {} +

That is, give the child shell (sh -c here) the pathnames as command line arguments, and then use these in the spawned shell to concatenate them as you would usually use shell variables.

(the ${name##*/} above is just to protect against GNU find which prepends ./ to the pathnames when using -execdir)

Related:

2
  • I cannot reproduce the reported behaviour on my FreeBSD system, but there are some changes to find that post-date it that may have introduced this problem.
    – JdeBP
    Commented Jul 3, 2018 at 11:56
  • @JdeBP Reproduced under csh.
    – Kusalananda
    Commented Jul 4, 2018 at 19:42
0

I was confused about the two last examples. Here, if find wouldn't replace the {}, then one would suppose it would leave it as-is, and the output would be (result):{}:

# /usr/bin/find . -maxdepth 1 -execdir echo "(result):"{}  \;
(result):

Maybe it's your shell that breaks those foo{} strings instead?

The tcsh I have seems to do just that:

$ tcsh -c 'echo foo{}bar '
foobar
$ tcsh -c 'echo foo {} bar '
foo {} bar

This was also mentioned in the comments on another question about quoting {}, GNU find and masking the {} for some shells - which? .

Workaround: put quotes around the strings that contain {}:

$ tcsh -c 'echo "foo{}bar" '
foo{}bar

(Another shell that needs quoting for {} is fish, but it would drop the lone {}, too.)

0

You may be better off using something like:

find . -type f -name 'data*' -print0 | xargs -0 -I % mv '%' 'OLD_%'

by using find -print0 | xargs -0, each filename is separated by a null byte, so spaces and other special characters in the filename will not result in the filename being split into two separate arguments.

You must log in to answer this question.

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