5

I have a directory with some subdirectories with filenames and directorynames that contain some dots:

$ ls -R
.:
dir  file.with.dots

./dir:
subdir.with.dots

./dir/subdir.with.dots:
other.file

I really struggle to catch all these files with find because I don't want to rename the current folder . and .. itself.

I also read how to Replace dots with underscores in filenames, leaving extension intact, but that doesn't really fit here, because the main problem is the recursive selection of the files.

I tried to replace the dots with find and tr:

find $(pwd) -depth -name '*.*'|while read f; do mv -iv "$f" '"'$(echo $f|tr '.' '_')'"' ; done

but this would give errors if in both, the path to the file and the file itself, contains dots

How do I rename and replace all occurrences of a dot with an underscore?

2
  • WARNING: In any event you should check that the destination filename does NOT exist prior to doing the mv. Otherwise, you might be removing something you needed to keep.
    – mdpc
    Aug 10, 2014 at 22:37
  • @mdpc: ok, now mv -iv will ask interactively if it should overwrite. But my try isn't good anyway, there is already a great answer below
    – rubo77
    Aug 11, 2014 at 8:56

1 Answer 1

10

With zsh:

autoload zmv
zmv '(**/)(*.*)' '$1${2//./_}'

Otherwise, if you have access to bash (though ksh93 or zsh will do as well), you could always do:

find . -depth ! -name '.*' -name '*.*' -exec bash -c '
  for file do
    dir=${file%/*}
    base=${file##*/}
    mv -i -- "$file" "$dir/${base//./_}"
  done' sh {} +

(though you'll miss the sanity checks done by zmv).

Those (intentionally) don't rename hidden files.

The point is to process the list depth-first (which zmv does by default, and using -depth in find) and only rename the basename of the file.

So that you do:

mv -- a.b/c.d a.b/c_d

and then:

mv -- a.b a_b

Also note that for the non-zmv solution, if you have a a.b file and a a_b directory in the same directory, then mv -- a.b a_b will happily move a.b into a_b/. To avoid that, if on a GNU system, you can add the -T option to mv.


As I see you tried to edit the answer to use a while read loop.

Note that you should generally avoid while read loops, even in this case where one command per line has to be run anyway. If you really want to use one, then doing it right is tricky and involves ksh/bash/zshisms:

{
  find . -depth ! -name '.*' -name '*.*' -exec printf '%s\0' {} + |
    while IFS= read -rd '' file; do
      dir=${file%/*}
      base=${file##*/}
      mv -i -- "$file" "$dir/${base//./_}"
    done <&3 3<&-
} 3<&0

(you can replace -exec printf '%s\0' {} + with -print0 if your find supports it).

You need:

  • -print0/-exec printf '%s\0' {} + as you can't rely on newline as the separator since newline is a valid character in a filename
  • as a result, you need -d '' (read reads until NUL instead of NL)
  • IFS= (that is remove all the whitespace characters from IFS for read as otherwise strips them from the end of the input record).
  • -r as otherwise read treats backslash as an escape character (for the field and record separator).
  • that litte dance with file descriptors because you want the standard input of mv to be the terminal (for the answers to the -i prompts), not the pipe from find.
9
  • what is the -- in mv stand for? and it seems like the -i option for interactive doesnt work here.
    – rubo77
    Aug 10, 2014 at 21:10
  • 1
    -- is a good habit to have. It marks the end of options. It's not needed here as all file paths start with ./ (and thus not - so can't be taken as options). zmv always calls mv -- ... as well. Aug 10, 2014 at 21:12
  • -i prompts you before overwriting a file (losing data). I added it here because we're not benefiting from the kind of sanity check that zmv does prior to start the job. -i would give you a chance to avoid a big mistake. Aug 10, 2014 at 21:14
  • I know about the interaction, but there is no question at my testserver using -i
    – rubo77
    Aug 10, 2014 at 21:17
  • 1
    @rubo77, you'd only get a question if for instance you had both a a.b and a_b in a given directory (though not if a_b itself is a directory as the mv -i -- a.b a/b would move a.b inside a_b (see the (GNU-specific) -T option to mv to avoid that)) Aug 10, 2014 at 21:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .