2

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!

DataDynamo
  • 23
  • 5

2 Answers2

4

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).

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • Thank you so much Gordon! I did a lot of searching and reading man pages and couldn't figure it out. I see the error or my ways <3. – DataDynamo Apr 23 '23 at 23:38
3

Like this, using Perl's rename, as far as you use current directory, no need find | xargs rename:

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

Take in account that you have leading ./ on each files

Remove -n switch, aka dry-run when your attempts are satisfactory to rename for real.


In a recursive way, without find:

shopt -s globstar # needed for bash, not zsh
rename -n 's|^\./\d{4}\s+||' ./**/*.txt
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223