1

I'm trying to add a prefix to all files that are not .sh files in the current directory, and all subdirectories, with bash using find.

The problem is that I get an error:

mv: cannot move './test.txt' to 'PRE_./test.txt': No such file or directory

My one-liner is:

find -type f ! -name "*.sh" -exec sh -c 'mv "{}" PRE_"{}"' _ {} \;

I tried xargs with rename, too:

find -type f ! -name "*.sh" -print0 | xargs -0 rename 's/(.*)$/PRE_$1/'

But I got the same error. What's wrong? Thanks in advance!

cxw
  • 16,685
  • 2
  • 45
  • 81
  • 3
    `cannot move './test.txt' to 'PRE_./test.txt'` seems like a pretty clear message - look at the new name you are trying to give it, is that what you wanted? You are prefixing the entire path, not just the filename. – Gareth Latty Sep 09 '16 at 12:50
  • Just in the current directory, or also in all subdirectories of the current directory? – cxw Sep 09 '16 at 12:51
  • Have a look at http://unix.stackexchange.com/questions/199301/find-files-based-on-name-and-moving-them-with-renaming-simultaneously – Jean-Baptiste Yunès Sep 09 '16 at 12:55
  • Oh, forgot to mention: i want to rename files in all subdirectories too. –  Sep 09 '16 at 12:56

5 Answers5

1

find returns the path from . all the way to the file, so each find result in starts with ./ when you begin your search in the current directory. As a result, your new filename is PRE_./test.txt, which is test.txt in a directory called PRE_.. Since there is no directory called PRE_., mv can't move test.txt into that directory, so gives you the error message you saw.

If you are only renaming files in the current directory, and not subdirectories, you can just use bash:

shopt -s extglob 
for f in !(*.sh) ; do [[ -d "$f" ]] || mv "$f" "PRE_$f" ; done

Thanks to this answer for the !(pattern) construct in bash, available when extglob is enabled. It returns all filenames not matching pattern.

For files in all subdirectories, too, use:

shopt -s extglob globstar
for f in **/!(*.sh) ; do [[ -d "$f" ]] || mv "$f" "${f%/*}/PRE_${f##*/}" ; done

Thanks to this answer for globstar. The % and ## chop the path components and stick the PRE_ into the right place, at the beginning of the filename.

Edit The [[ -d "$f" ]] || in both of the above prevents the loop from renaming directories.

Community
  • 1
  • 1
cxw
  • 16,685
  • 2
  • 45
  • 81
1

If you dont want to change files in subdirectories you can use this:

find -type f ! -name "*.sh" -printf "%f\n" | xargs -r rename 's/(.*)$/PRE_$1/'

the problem was ./ infront of the file names.

Farhad Farahi
  • 35,528
  • 7
  • 73
  • 70
1

It's good practice to not use {} directly in the argument to sh -c, but to pass its value in as an argument. Then you can use parameter expansion operations as usual to modify its value. Here, we'll split the argument into its directory and file name components, then reassemble them including the filename prefix PRE_ to use as an argument to mv.

script='
d=${1%/*}
f=${1#$d}
mv "$1" "$d/PRE_$f"
'
find -type f ! -name "*.sh" -exec sh -c "$script" _ {} \;

(This is just a slight change from your original attempt; you were passing {} as an argument, but not using it. Instead, you were "embedding" the value directly into the string passed to sh.)

You can also use the -exec + form to minimize the number of calls to sh, by passing multiple file names and iterating over them in the shell. (This starts to converge with the pure bash solution suggested by @cxw, but maintaining POSIX, as well as bash 3.x, compatibility):

script='
for arg;
 d=${arg%/*}
 f=${arg#$d}
 mv "$1" "$d/PRE_$f"
done
'
find -type f ! -name "*.sh" -exec sh -c "$script" _ {} +
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Won't this try to move `./foo/bar/bat` to `./PRE_foo/bar/bat` rather than `./foo/bar/PRE_bat`? It looks to me like `$f` will get `foo/bar/bat`. (I may, of course, be missing something.) – cxw Sep 09 '16 at 13:26
  • Yeah. I'll fix that. – chepner Sep 09 '16 at 13:27
1

On many Linux distributions, the rename command is not available by default.

If your system is missing the rename command, install it with:

For Ubuntu and Debian, use

sudo apt install rename

For CentOS and Fedora, use

sudo yum install prename
Ihor Baklykov
  • 543
  • 7
  • 24
alonegame
  • 49
  • 4
0
find -type f ! -name "*.sh" -exec rename -n 's|.*/\K|PRE_|' {} +
  • .*/ matches upto last / in file name
  • by using \K lookbehind, we can avoid need of capture group in this case

Remove -n option from rename once it shows correct renaming

Sundeep
  • 23,246
  • 2
  • 28
  • 103