How to find a file and move it into the directory it was found in

3

I am trying to rename files in sub directories using xargs by running the following command:

find . -name oldfile.txt -print0 | xargs -n 1 -0 -I % mv % newfile.txt

The problem is that the file gets moved into the directory I run the command in instead of the directory that oldfile.txt is found in. I cant seem the solution for this, any ideas?

I'm thinking that perhaps I can get the directory from the found file's path and then append it to the front of the second paramater of mv but how would that work?

Chris Bier

Posted 2013-05-17T19:08:07.037

Reputation: 129

Answers

3

Instead of relying on external commands, where you lose the information about the current directory, you can use find's -execdir option to have it execute commands in the directory a file was found in.

find . -name oldfile.txt -execdir mv -- {} newfile.txt \;

This is actually a more secure version of -exec that is preferred in most situations. The only drawback is that it's not specified by POSIX, so you won't find it in all find variants – but at least in GNU find and BSD find you can.

If you want to be extra safe, you should call mv like mv -- source target so that a file starting with a dash isn't incorrectly interpreted as an argument to mv.


If for some reason you don't want to use -execdir or have other complicated things to do, or just want to learn a little about Bash, here's how to get the folder and move the file based on that:

find . -name oldfile.txt -exec bash -c 'mv -- "${0}" "$(dirname $0)"/newfile.txt' {} \;

Here, {} passes the full path to bash as $0. We get its directory name through command substitution ($(…)) and then append the new name. Both the original argument and the directory name have to be double quoted to prevent whitespace from breaking the command – otherwise mv would think you're trying to move two files called foo and bar when the file is actually called foo bar, for example.

slhck

Posted 2013-05-17T19:08:07.037

Reputation: 182 472

This worked! Excellent solution. The third paramater of mv is the directory? I've never seen mv have a third param. I looked at the manual but it doesn't seem clear. – Chris Bier – 2013-05-17T19:28:37.943

No, it has nothing to do with mv. The \; is part of the -exec / -execdir option – see the find manual I linked you to. You always have to include it in order to have find recognize the end of the command specified in exec. – slhck – 2013-05-17T19:29:51.523

Ah I see! Both of these constructions need to be escaped (with a ‘\’) or quoted to protect them from expansion by the shell. That means that {} should be escaped as well. – Chris Bier – 2013-05-17T19:31:47.283

You don't really have to escape or quote {}, it also works without that in most shells or find versions. Quoting {} is only necessary when it would be interpreted otherwise by the shell, which usually isn't the case. I think @DanielBeck recently had a problem with the fish shell and that – but I'm not entirely sure if fish was the whole cause of the issue. – slhck – 2013-05-17T19:37:25.023

Added another example of how to achieve what you need, albeit a little more complicated than it needs to be. – slhck – 2013-05-17T19:44:57.400