Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free.

I am trying to rename all files in a folder replacing underscores with spaces.

i.e. this_is_a_test --> this is a test

but somehow I'm messing up the quoting

> for file in * ; do echo mv -v $file $(echo $file | sed 's/_/\\ /g') ; done
mv -v this_is_a_test this\ is\ a\ test

that looks OK, but if I remove the 'echo' mv complains as if the backslashes were removed

> for file in * ; do mv -v $file $(echo $file | sed 's/_/\\ /g') ; done
mv: target ‘test’ is not a directory

Can someone point out the error of my ways?

share|improve this question

5 Answers 5

up vote 2 down vote accepted

There is a minor mistake. Use "$newfile" instead of only $newfile. You need to use "

Here is the correct one.

for file in * ; do mv -v "$file" "$(echo $file | sed 's/_/\\ /g')" ; done

If you have filename this_is_a_test it will rename file to this\ is\ a\ test.

In case if you want to rename the file to this is a test. Use the code below,

for file in * ; do mv -v "$file" "$(echo $file | sed 's/_/ /g')" ; done

It is a good practise to use variables inside "" while you writing good shell script.

share|improve this answer
    
Many thanks for the help, you solved the problem! –  ottotts Dec 14 '13 at 9:59
1  
Old answer, however just for clarity since its becoming popular again, this will break filenames containing newlines –  kos Jun 15 at 18:34

Instead of mv -v "$file" "$(echo $file | sed 's/_/\\ /g')" you can use a much simpler command mv "$f" "${f//_/ }"

To add it in your script:

for file in * ; do mv "$f" "${f//_/ }" ; done

And that's is

share|improve this answer
    
This is pretty much ok, but it will break filenames containing newlines –  kos Jun 15 at 18:29
1  
@kos no it won't. Shell globs can deal with newlines with no problem. Souravac's answer will break on newlines but only because of sed. –  terdon Jun 17 at 22:01
    
@terdon I tought it would have break on the expansion pretty much like Souravac's answer on the replacement, that's true. @ Maythux Apologies, +1 then –  kos Jun 18 at 0:46

Using rename:

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

If everything is ok, remove the -n switch:

rename 's/_/ /g' *
~/tmp$ tree
.
├── file
├── file_1
├── file_2
├── file_3
└── file_with_underscores

0 directories, 5 files
~/tmp$ rename 's/_/ /g' *
~/tmp$ tree
.
├── file
├── file 1
├── file 2
├── file 3
└── file with underscores

0 directories, 5 files
share|improve this answer
    
The question gets an unusual popularity =) +1 –  A.B. Jun 15 at 16:20
    
I like your tree command … Installed =) –  A.B. Jun 15 at 16:39

Use the find and rename command:

find <your_start_folder> -type f -regex ".*_+.*" -exec rename 's/_/ /g' {}  \;

This command renames all file with a _ in the filename recursively.

Explanation

  • -regex ".*_+.*"

    Find all files with at least one _ in the filename

  • _

    Replace all occurrences of _

  • … with a space character ()


Example

% ls -Rog
.:
total 4
drwxrwxr-x 2 4096 Jun 15 17:39 foo
-rw-rw-r-- 1    0 Jun 15 17:34 foo_bar

./foo:
total 0
-rw-rw-r-- 1 0 Jun 15 17:32 foo_bar

% find . -type f -regex ".*_+.*" -exec rename 's/_/ /g' {} \;

% ls -Rog                                                                 
.:
total 4
drwxrwxr-x 2 4096 Jun 15 17:40 foo
-rw-rw-r-- 1    0 Jun 15 17:34 foo bar

./foo:
total 0
-rw-rw-r-- 1 0 Jun 15 17:32 foo bar
share|improve this answer
    
Hmmm this works only if there are maximum two underscores in the filename. –  kos Jun 15 at 16:30
    
@kos DOH I'm getting old –  A.B. Jun 15 at 16:37
    
@kos ok, fixed =) –  A.B. Jun 15 at 16:49
1  
+1 for a recursive way, however OP's script applies also to directories. Also -regex is not really needed here, -name "*_*" is enough –  kos Jun 15 at 17:01

Here's an awk + for loop version:

for filename in *; do NEWNAME=$(echo "$filename" | awk '{gsub("_"," "); print}'); mv "$filename" "$NEWNAME";done

And here it is in action

$ ls                                                                                                                                                  

$ echo "TEST" | tee {foo,bar}_{yolo,swag}_{whatever,else}.txt                                                                                         
TEST

$ ls
bar_swag_else.txt      bar_yolo_else.txt      foo_swag_else.txt      foo_yolo_else.txt
bar_swag_whatever.txt  bar_yolo_whatever.txt  foo_swag_whatever.txt  foo_yolo_whatever.txt

$ for filename in *; do NEWNAME=$(echo "$filename" | awk '{gsub("_"," "); print}'); mv "$filename" "$NEWNAME";done                                    

$ ls
bar swag else.txt      bar yolo else.txt      foo swag else.txt      foo yolo else.txt
bar swag whatever.txt  bar yolo whatever.txt  foo swag whatever.txt  foo yolo whatever.txt

To account for newlines and possible renaming of directories, here's the typical IFS + find + read + while loop construct. Once suggestion through, run first the version with find . -type d -print0, because if you start renaming files first, and a subdirectory contains underscore, the filename won't be changed. In short, take care of directories and subdirectories first, then take care of the files

$ ls
file_bar_swag.txt  file_bar_yolo.txt  file_test_swag.txt  file_test_yolo.txt  foo_bar_swag.txt  foo_bar_yolo.txt  foo_test_swag.txt  foo_test_yolo.txt  tester_dir

$ ls tester_dir/                                                                                                                                                                                         
ber_sweg_elze.txt  ber_sweg_whut.txt  ber_yelo_elze.txt  ber_yelo_whut.txt  fee_sweg_elze.txt  fee_sweg_whut.txt  fee_yelo_elze.txt  fee_yelo_whut.txt

$  find . -type d -print0 | while IFS="" read -r -d "" filename ; do NEWNAME=$(echo "$filename" | awk '{gsub("_"," "); print}'); mv "$filename" "$NEWNAME";done 
mv: ‘.’ and ‘./.’ are the same file

$ ls
file_bar_swag.txt  file_bar_yolo.txt  file_test_swag.txt  file_test_yolo.txt  foo_bar_swag.txt  foo_bar_yolo.txt  foo_test_swag.txt  foo_test_yolo.txt  tester dir

$  find . -type f -print0 | while IFS="" read -r -d "" filename ; do NEWNAME=$(echo "$filename" | awk '{gsub("_"," "); print}'); mv "$filename" "$NEWNAME";done                                          
mv: ‘./.yolo’ and ‘./.yolo’ are the same file

$ ls
file bar swag.txt  file bar yolo.txt  file test swag.txt  file test yolo.txt  foo bar swag.txt  foo bar yolo.txt  foo test swag.txt  foo test yolo.txt  tester dir

As you can see, there's a small problem - because the find command lists current directory as well ( symbolized with a .), the command will create a byproduct - a file with a leading dot.

Adding ! -path . when you deal with directories solves the problem

$ find . ! -path .  -type d -print0 | while IFS="" read -r -d "" filename ; do NEWNAME=$(echo "$filename" | awk '{gsub("_"," "); print}'); mv "$filename" "$NEWNAME";done                                

$ find .  -type f -print0 | while IFS="" read -r -d "" filename ; do NEWNAME=$(echo "$filename" | awk '{gsub("_"," "); print}'); mv "$filename" "$NEWNAME";done                                          

$ ls
file bar swag.txt  file bar yolo.txt  file baz swag.txt  file baz yolo.txt  foo bar swag.txt  foo bar yolo.txt  foo baz swag.txt  foo baz yolo.txt  tester dir

$ ls "tester dir"
file bar.txt  file baz.txt  foo bar.txt  foo baz.txt

$ ls -a
.  ..  file bar swag.txt  file bar yolo.txt  file baz swag.txt  file baz yolo.txt  foo bar swag.txt  foo bar yolo.txt  foo baz swag.txt  foo baz yolo.txt  tester dir
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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