Search code examples
linuxbashfindrename

Why doesn't a combined find and rename command work as expected under bash shell?


I have a folder full of text files of the format 'four digits, space, alphanumeric string.txt'. For example:

$ touch '0001 jko0001.txt'  '0002 jko0002.txt'  '0003 jko0003.txt'

$ ls
'0001 jko0001.txt'  '0002 jko0002.txt'  '0003 jko0003.txt'

I would like to rename the files so leading digits and space are removed. Since I have a lot of files, I am using find to pass filenames to rename. I attempted to do this with the following command:

find . -type f -name '*.txt' -print0 | xargs -0 rename -n -- 's/^\d{4}\s+//' {} +

However, this fails. (Yes, -n is only to print out changes without renaming the files. It fails even if I remove it).

Interestingly, if I split the command into pieces, it does work:

$ find . -type f -name '*.txt'
./0003 jko0003.txt
./0002 jko0002.txt
./0001 jko0001.txt

$ rename -n -- 's/^[0-9]{4}\s+//' *.txt
0001 jko0001.txt -> jko0001.txt
0002 jko0002.txt -> jko0002.txt
0003 jko0003.txt -> jko0003.txt

$ bash --version
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)

But when combined with xargs, it fails. Why?

Also, I can't even get it working with find's -execdir:

find . -type f -name '*.txt' -execdir rename -n -- 's/^\d{4} //' {} \;

None of these work.

find . -type f -name '*.txt' -print0 | xargs -0 rename -n -- 's/^\d{4}\s+//' "{}" +
find . -type f -name '*.txt' -print0 | xargs -0 rename -n -- 's/^[0-9]{4}\s+//' "{}" +
find . -type f -name '*.txt' -execdir rename -n -- 's/^\d{4} //' {} \;
find . -type f -name '*.txt' -execdir rename -n -- 's/^\d{4} //' '{}' \;

Thanks in advance!


Solution

  • There are at least two problems here. First, find is passing a path to the file, not just the filename (yes, even with -execdir). So add the -d option to rename, to tell it to just act on just the filename, not the full path.

    Second, you're mixing find -exec syntax up with xargs. Specifically, the {} + at the end of the command is something you'd use with find -exec, not with xargs (note: in some modes, xargs uses {} like this, but it never uses +). To fix it, either remove the {} + and use standard xargs syntax:

    $ find . -type f -name '*.txt' -print0 | xargs -0 rename -n -d -- 's/^\d{4}\s+//'
    rename(./0003 jko0003.txt, ./jko0003.txt)
    rename(./0001 jko0001.txt, ./jko0001.txt)
    rename(./0002 jko0002.txt, ./jko0002.txt)
    

    Or skip xargs, and use find -exec directly (this time with the {} +):

    $ find . -type f -name '*.txt' -exec rename -n -d -- 's/^\d{4}\s+//' {} +
    rename(./0003 jko0003.txt, ./jko0003.txt)
    rename(./0001 jko0001.txt, ./jko0001.txt)
    rename(./0002 jko0002.txt, ./jko0002.txt)
    

    BTW, when troubleshooting problems like this, it's sometimes helpful to put echo in front of the problem command to get an idea what arguments are being passed to it:

    $ find . -type f -name '*.txt' -print0 | xargs -0 echo rename -n -- 's/^\d{4}\s+//' {} +
    rename -n -- s/^\d{4}\s+// {} + ./0003 jko0003.txt ./0001 jko0001.txt ./0002 jko0002.txt
                               ^^^^ this is the problem
    

    But that's sometimes misleading, since (among other things) it loses the distinction between spaces within arguments and spaces between arguments. Replacing echo with printf '%q\n' is sometime better (although it has other problems, and not all external printf implementations support %q).