I have a large unix fileserver, with a single large SMB share. It serves to Mac, Linux and windows clients. A large portion of the files on this box have been migrated there from a Mac Xserve. One of the ways the share is backed up is via a Windows box. That particular backup software fails if there are characters it doesn't like in the filenames.

I've managed to fix most of the filename issues but am struggling with files that end in a dot. For example:

  • filename.
  • newfile.txt.
  • stupid old file.

I am trying to find some way to batch rename all the files that end in a dot to the same name without the dot. So filename. becomes filename

find . -name "*."

works to find the files. I've tried piping it through sed:

find . -name "*." | sed 's/.$//'

which does the job in the console but doesn't actually rename the files. That's the part I'm struggling with.

  • 1
    Please see my answer here regarding the rename command which will do what you need. Note that there's another rename command which uses different syntax, but it will do what you need, too. – Dennis Williamson Mar 26 '14 at 2:08
  • Hi Dennis, thanks for the response. I intended. and neglected, to mention in the question, but I can't get rename to deal with the trailing dot I'm having a hard time understanding the way it deals with wildcards. – user3408072 Mar 27 '14 at 15:41
  • @user3408072 Looks like you're new to serverfault. Please remember to upvote answers you like and to accept the answer that you think works best – Daniel Lawson Mar 28 '14 at 2:13

If you have the Perl script version of rename (called either rename or prename):

rename 's/\.$//' *.

or the util-linux version (called either rename or rename-ul):

 rename . '' *.

If you run the command without arguments and get something similar to:

Usage: rename [-v] [-n] [-f] perlexpr [filenames]

then it's the Perl script version.

Something like:

rename.ul: not enough arguments

Usage: rename.ul [options] expression replacement file...

Options: -v, --verbose explain what is being done -V, --version
output version information and exit -h, --help display this help and exit

means it's the util-linux version.

  • Nice, I didn't know about rename. Much simpler. – Daniel Lawson Mar 27 '14 at 20:32

Kinda hacky but should work:

If you want to rename both files AND directories with trailing dots:

    find . -name "*." -print0 | xargs -0 -n 1 my_rename.sh

OR if you want to rename only the files:

    find . -name "*." -type f -print0 | xargs -0 -n 1 my_rename.sh

Where my_rename.sh script is just:

mv "$1" "${1%%.}"

(Also don't forget to make the script executable: chmod +x my_rename.sh)2

  • This gave me find: paths must precede expression Usage: find [-H] [-L] [-P] [path...] [expression] mv: target `' is not a directory: No such file or directory When I changed it to 'find . -type f -name '*.' -print0 | xargs -0 -n 1 ./my_rename.sh' That worked. Thanks for the help! – user3408072 Mar 27 '14 at 15:47
  • Sorry I should have been more explicit about my [] syntax shortcut for -type f being optional depending on whether or not you wanted to hit only files, or include dirs as well. – DouglasDD Mar 27 '14 at 18:12

This looks like it should do the job:

find . -type f -name '*\.' -exec bash -c 'mv "$1" "${$1%.}"' _ {} \;

This limits the find to only files with a period at the end (otherwise it matches the current working directory, '.'), and then executes the quoted shell snippet (mv "$1" "${$1%.}") for each filename it finds. Within the shell snippet it uses bash parameter expansion and substitution to remove the trailing '.'. The other line noise at the end is required for the find -exec: {} is the matched text from the find, \; is required to end the exec string, and I believe the _ tells the exec to pass {} in as a parameter ($1) to the sh command. To be honest, this specific usage of -exec is new to me, I found it here.

The bash substitution bit is pretty straight forward, and I use this quite often for renaming files, e.g. FILENAME=foo.JPG; mv ${FILENAME} ${FILENAME%.JPG}.jpg will remove the '.JPG' from FILENAME and replace it with '.jpg'

If you still want to use sed rather than bash expansion, this looks like it'd do the job:

find . -type f -name '*\.' -exec sh -c 'mv "{}" "$(echo {} | sed s/\.$// )"' \;

I first tried to solve this without causing -exec to invoke sh -c, but that didn't like expanding {} out while attempting to do the sed (or with the bash substitution example earlier).

Also note that you have to put " " around either the filenames you're expanding (e.g., {} or $1, depending on the example), as your sample files have spaces in them. This may still break depending on what special characters are in your file (e.g., a " in the filename will cause problems).

  • I couldn't get the first example with the shell expansion/substitution to work. Kept getting _: ${$1%.}: bad substitution Played around a bit and just couldn't make it work, but the sed version worked great. Something weird with bash on this box, the example from DouglasDD that worked had to be tweaked a bit too. Thanks for the help! – user3408072 Mar 27 '14 at 16:01
  • 1
    Should sh from the first command be replaced with bash because ${$1%.} is Bash specific as far as I know? – Cristian Ciupitu Mar 27 '14 at 22:20
  • @CristianCiupitu: That might be it. I pulled my example from the link I referred to on askubuntu.com, and I tested on ubuntu. Looks like dash is sufficiently similar to bash in this case, but it might not be on other systems – Daniel Lawson Mar 28 '14 at 2:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.