How does this find state actually work in moving a file up one directory?

3

1

Can someone explain how the string manipulation in this find statement actually works? My head is about to explode trying to figure it out. :-o

From: UNIX shell scripting: how to recursively move files up one directory?

find ".mp3" -type f -exec sh -c 'mv -i "$1" "${1%/*}/.."' sh {} \;

The above line moves all files with the suffix .mp3 up one directory exactly, which is exactly what I want. Wanting to learn more about shell scripting, I'm trying to understand (well, pretty much everything) in the above statement.

The mv statement with the $1 and ${1%/*}/..

Why the sh {} at the end? Why two shs as well?

Any breakdown of the syntax would be great. I've looked at Bash Manipulation docs and I am pretty confused especially about the "$1" "${1%/*}/.." part.

Richard Westby-Nunn

Posted 2020-02-17T00:50:01.227

Reputation: 31

1

"Why two shs as well?" – Recently I have explained this (in comments) twice; and this is only "recently". Answering this part of your question would be the nth time for me (this doesn't mean you did something wrong). Therefore I created a separate question and answered it decently.

– Kamil Maciorowski – 2020-02-17T22:54:13.770

Answers

1

Firstly, that does not work as you stated. It will find only the regular file .mp3 (not all files with that extension) or, if .mp3 is a directory, recursively look inside it, finding all regular files below it. You could use -name "*.mp3" instead to find all .mp3 regular files below your directory.

Now, the command in the answer you referred is

find A -type f -exec sh -c 'mv -i "$1" "${1%/*}/.."' sh {} \;

and is allegedly used for the "standard find", which is the POSIX find, not the GNU find you probably have in your system. There are some differences between them, so let the POSIX find manual guide us here.

The command finds regular files recursively, starting from directory A, or, if there is no such directory, it looks for regular file A in the current directory.

Now the complicated part. According to the manual, the -exec option is of the form

-exec utility_name [argument ...] ;

See that sh is used as utility_name. sh -c means "execute the following string", which is

mv -i "$1" "${1%/*}/.."

  • $1 is the found file, provided by {} .
  • $0 is the second sh (aha!), so this sh here is really doing nothing, just a placeholder.
  • ${1%/*}/.. means: fetch $1, delete the shortest /* match from the end of it (which is slash + its basename), append /.. to the result, and the final result is its parent directory! For example, A/B/Music/x.mp3 undergoes this transformation: A/B/Music/x.mp3A/B/MusicA/B/Music/..A/B.

Therefore, at the end you got a neat POSIX way to move up that file using find.


More on the % symbol that showed up (and also its friend #): https://stackoverflow.com/a/34952009

Quasímodo

Posted 2020-02-17T00:50:01.227

Reputation: 239