Is there a way with only the find command to prepend a string to filenames?

So that this:


Becomes this:



find . -name '201*' -execdir mv {} foo_{} \;

Does not work because the {} part includes the leading ./ in the filename, and therefore tries to write to foo_./201*.

If this is not possible with only find, what is the most portable (read: only coreutils, no shell scripts, easiest to understand) way to prepend a string to filenames?

  • 1
    "no shell scripts" . . . are you ruling out for example -execdir sh -c 'mv "$1" "foo_${1#*/}"' sh {} \; ? Commented Apr 12, 2017 at 18:01
  • @steeldriver only if it is not possible without sh and/or coreutils.
    – iquoth
    Commented Apr 12, 2017 at 18:09
  • @steeldriver what does the 1#*/ part do?
    – iquoth
    Commented Apr 12, 2017 at 18:10
  • 1
    does it have to be with the find? How about a for loop that executes on every file with said "201*" string? for f in 201*; do mv $f "foo_$f"; done
    – bretonics
    Commented Apr 12, 2017 at 18:13
  • ${1#*/} is the value of the first positional parameter with the shortest leading component that matches */ removed. AFAIK it's a feature of POSIX sh and so should be fairly portable - see for example 2.6.2 Parameter Expansion Commented Apr 12, 2017 at 18:16

5 Answers 5


No. But the rename command offers a simple solution.

$ ls -1
$ rename 201 foo_201 201*
$ ls -1

How about

find . -name '201*' -execdir basename {} \; | xargs -I{} mv {} foo_{}

basename is provided by coreutils and since xargs is provided by findutils it should be at least as portable as find -execdir itself.

Alternatively, using only POSIX shell features

find . -name '201*' -execdir sh -c 'mv "$1" "foo_${1#*/}"' sh {} \;
  • Your xargs solution fails if the file or dir contains: touch '"*'" '" (two spaces).
    – Ole Tange
    Commented Apr 13, 2017 at 20:24

This is not possible with find alone, not even with GNU find. GNU find can print files with the command line prefix removed and another prefix put instead, by using -printf foo_%p instead of -print, but there's nothing similar for -exec.

(You could use find -printf 'mv foo_%p …' | sh, if you like living dangerously. This only works with “tame” file names and breaks horribly if there are spaces, quotes and other special characters in the file names. So don't do this.)

The standard way to do this (as in a way that works on any POSIX system, and also as in a common way) is to call a shell to do the string manipulation.

find . -name '201*' -exec sh -c 'mv -- "$0" "${0%/*}/foo_${0##*/}"' {} \;

I don't use -execdir because it's a GNU extension and you asked for portability. Note that {} is passed to the shell as an argument. Never use {} inside the shell code: not only because it isn't portable, but most importantly because it would cause the file name to be interpreted as shell code, which fails if the file name contains special characters.

On modern POSIX systems (anything from the last decade), you can speed this command up a little by batching the shell calls.

find . -name '201*' -exec sh -c 'for x do mv -- "$x" "${x%/*}/foo_${x##*/}"; done' sh {} +

Alternatively, in ksh, bash or zsh, you can use recursive globbing instead of calling find.

set -o globstar # ksh only
shopt -s globstar # bash only
for x in **/201*; do
  mv -- "$x" "${x%/*}/foo_${x##*/}"

With GNU Parallel it looks like this:

# Make an annoying dir and a couple of annoying files
mkdir '"*'"  '"
touch '"*'"  '"/201'"*'"  '".txt
touch '"*'"  '"/201'"*'"  '".png

# GNU Parallel deals nicely with annoying files
find . -name '201*' | parallel mv {} {//}/foo_{/}

You could use an asterisk (*) instead which strips the ./ part:

find * -name '201*' -exec mv {} foo_{} \;  

You must log in to answer this question.

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