3

I use rename to underscore spaces in filenames, simply with:

rename "s/ /_/g" * 

but I encounter the problem that files downloaded from the internet often have multiple spaces. A nasty workaround I used (but works only for 3 spaces which in most cases is enough), but there has to be a more elegant approach than:

rename "s/   /_/g" *; rename "s/  /_/g" *; rename "s/ /_/g" *
2
  • 4
    Why not rename 's/ [ ]*/_/g' * ? – Thomas Dickey Dec 27 '18 at 19:27
  • that is my answer, THX :-) – nath Dec 27 '18 at 19:28
7

The following worked for me:

rename 's/\s+/_/g' *

It will match one to unlimited instances of white space

Note this would also work for newlines and tabs, however based on your use case I think that would be preferable and not unwanted? But to match only space specifically you could do:

rename 's/ +/_/g' *
1
  • 1
    even simpler, THX – nath Dec 27 '18 at 19:30
1

IDK if rename can work recursively. I made some empty files with space(s) in their names inside a subdirectory without a space and one with. The below works for me to rename files with one or more spaces in their names with one underscore, and doesn't bother the subdirectory with spaces in its name.

root@server <1>: /cwd# mkdir subdir1 'sub dir 2' ; touch 'subdir1/file 1' 'sub dir 2/file  2  ' 'subdir1/ f i l e 3 ' 'sub dir 2/   f  i  l  e   4   '

root@server <2>: /cwd# ls -lhR
.:
total 0
drwxr-xr-x 2 root root 80 Dec 28 00:42  subdir1
drwxr-xr-x 2 root root 80 Dec 28 00:42 'sub dir 2'

./subdir1:
total 0
-rw-r--r-- 1 root root 0 Dec 28 00:42 'file 1'
-rw-r--r-- 1 root root 0 Dec 28 00:42 ' f i l e 3 '

'./sub dir 2':
total 0
-rw-r--r-- 1 root root 0 Dec 28 00:42 'file  2  '
-rw-r--r-- 1 root root 0 Dec 28 00:42 '   f  i  l  e   4   '

root@server <3>: /cwd# find . -type f -exec echo {} \; | tee ../FILES.txt
./sub dir 2/   f  i  l  e   4
./sub dir 2/file  2
./subdir1/ f i l e 3
./subdir1/file 1

root@server <4>: /cwd# while IFS= read line ; do
> dirname="${line%/*}"
> fn=${line##*/}
> fn="${fn//+( )/_}"
> mv -v "$line" "${dirname}/${fn}"
> done < ../FILES.txt
renamed './sub dir 2/   f  i  l  e   4   ' -> './sub dir 2/_f_i_l_e_4_'
renamed './sub dir 2/file  2  ' -> './sub dir 2/file_2_'
renamed './subdir1/ f i l e 3 ' -> './subdir1/_f_i_l_e_3_'
renamed './subdir1/file 1' -> './subdir1/file_1'

root@server <5>: /cwd# ls -lhR
.:
total 0
drwxr-xr-x 2 root root 80 Dec 28 00:42  subdir1
drwxr-xr-x 2 root root 80 Dec 28 00:42 'sub dir 2'

./subdir1:
total 0
-rw-r--r-- 1 root root 0 Dec 28 00:42 file_1
-rw-r--r-- 1 root root 0 Dec 28 00:42 _f_i_l_e_3_

'./sub dir 2':
total 0
-rw-r--r-- 1 root root 0 Dec 28 00:42 file_2_
-rw-r--r-- 1 root root 0 Dec 28 00:42 _f_i_l_e_4_

Some notes on each command:

  1. creates two subdirectories, where the second has spaces in its name.

  2. lists the contents recursively. note the single quotes are used on files or folders having spaces.

  3. use find to find files (-type f) and echo the name to "FILES.txt" in the parent dir.

  4. use while loop to iterate over each line in the file. IFS= clears the input field separator so leading and trailing whitespace isn't truncated.

    4.1 extracts the directory name, which shouldn't be modified

    4.2 gets the file name

    4.3 replaces one or more consecutive space chars with a single underscore in the file name

    4.4 verbosely renames the files, but not the folders.

  5. lists the contents recursively. note the single quotes are used on files or folders having spaces.

I used parameter expansion for getting the dir names and file names instead of dirname and basename because it's faster.

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.