6

For all files in a directory, I want to replace the underscores in the filename with spaces.

I tried this solution, which does the opposite of what I want: https://stackoverflow.com/questions/1806868/linux-replacing-spaces-in-the-file-names

But switched the space with the underscore. That does not work, giving the error

´x´ is not a directory

Where x is the last word in the filename, for example hello_world_x

What is the correct command to replace underscores with spaces for all files in a directory?

  • 2
    It is a terrible ideia to have spaces in filenames, I would prefer a lot _, even for scripting. – Rui F Ribeiro Dec 02 '15 at 18:27
  • 1
    I deleted the comments but he really did have a point. Please don't do this. File names with spaces are a very bad idea and will make anything you need to do down the line more complex for no reason. I strongly urge you to reconsider and avoid using spaces. – terdon Dec 24 '15 at 10:20
  • And, this should still be linked here: http://unix.stackexchange.com/q/131766/135943 (@terdon: Thanks for the good moderation. :) – Wildcard Dec 30 '15 at 07:45

3 Answers3

12

Plagiarizing the code from the answer you linked to and making it more robust:

for file in *; do mv "$file" "$(echo "$file" | tr '_' ' ')" ; done

Quoting ensures that each file name is passed to mv as a single token, rather than it being broken at space boundaries.

If you have access to the Perl-based rename utility, the following will work as well:

rename -n 's/_/ /g' *

(Remove the -n switch after confirming that the preview corresponds to what you would like to do.)

dhag
  • 15,736
  • 4
  • 55
  • 65
  • 2
    This will execute a move (mv) for all files whether they have _ or not. Calling an external utility (tr) for something bash could do on its own is also not such a nice idea. –  Dec 02 '15 at 18:59
  • @BinaryZebra: Agreed. I made no effort to pass judgment on whether the request or referenced code are sane. – dhag Dec 02 '15 at 19:30
12

After you cd to the correct directory, this script will reliably solve your need (not portable because of the ${var//pat/str} expansion):

#!/bin/bash

set -- *_*
for file; do
    mv -- "$file" "${file//_/ }"
done

*_* The glob *_* will select all files that have an _ in their names.

set -- Those names (even including spaces or new-lines) will be reliably set to the positional parameters $1, $2, etc. with the simple command set -- "list"

for file; Then, each positional parameter will be (in turn) assigned to the var file.

do ... done contains the commands to execute (for each $file).

mv -- "$file" "${file//_/ }" will move (rename) each file to the same name with each (all) _ replaced by (space).

Note: You may add the -i (interactive) option to avoid overwriting already existing files. If the file exist, mv will ask. With a caveat: there needs to be an interactive shell where mv could communicate with the user.

mv -i -- "$file" "${file//_/ }"
  • 1
    @Wildcard Probably a better answer now. Changed to -i. –  Dec 21 '15 at 08:48
  • Great answer; I'd never used the bash variable substitution feature. One note about mv: when the target name exists already as a directory, mv will simply put the file in that directory without renaming either the file or the directory. Neither -i nor -n will prevent this behavior; you would have to check for the existence of the target first. (For the question asked about, this is an extreme edge case and more trouble than it's worth to check for, but for production environments it's good to be aware of.) If this is unclear: touch file1; mkdir file2; mv -i -n file1 file2 – Wildcard Dec 21 '15 at 09:18
  • 1
    Yes @Wildcard the file will be moved, but, as no file was erased, no harm done. Having a directory with the (almost) same name of a file is just calling for trouble. –  Dec 21 '15 at 09:27
1
find . -depth -name "*_*" -exec \
    sh -cf '
        for f do   IFS=_
            IFS=\  set "${f%/*}/"${f##*/}$0
            mv "$f" "$*"
        done
    ' _ {} +

a POSIX sh will expand away the changes as configured with its Internal Field Separator.

mikeserv
  • 58,310