0

I have been trying to write a script to rename all files that contain a space and replace the space with a dash.

Example: "Hey Bob.txt" to "Hey-Bob.txt"

When I used a for-loop, it just split up the file name at the space, so "Hey Bob.txt" gave separate argument like "Hey" and "Bob.txt".

I tried the following script but it keeps hanging on me.

#!/bin/bash
find / -name '* *' -exec mv {} $(echo {} | sed 's/ /-g')\;

  • almost certainly a quoting problem, but I'm not sure if quoting `{}` is needed or will help. Worth trying, right? Else, go back to your loop solution and dbl-quote all references to variables. Good luck. – shellter Jan 21 '14 at 04:55

3 Answers3

1

Building off OP's idea:

find ${PATH_TO_FILES} -name '* *' -exec bash -c 'eval $(echo mv -v \"{}\" $(echo {} | sed "s/ /-/g"))' \;
  • NOTE: need to specify the PATH_TO_FILES variable

EDIT: BroSlow pointed out need to consider directory structure:

find ${PATH_TO_FILES} -name '* *' -exec bash -c 'DIR=$(dirname "{}" | sed "s/ /-/g" ); BASE=$(basename "{}"); echo mv -v \"$DIR/$BASE\" \"$DIR/$(echo $BASE | sed "s/ /-/g")\"' \; > rename-script.sh ; sh rename-script.sh 
csiu
  • 3,159
  • 2
  • 24
  • 26
  • Would add `-type f` to avoid errors, but +1 for fancy one liner. – Reinstate Monica Please Jan 21 '14 at 05:22
  • I thought about that, but then thought: "What if OP wants to rename 'Hey Bob' directories?" or even 'Hey Bob' symbolic links :) – csiu Jan 21 '14 at 05:34
  • 1
    Maybe what he wanted, but it's going to completely change directory trees and you still need to make nonexistent directories before you mv files there in the one liner somehow. So e.g. "/home/foo/some space dir/some file" will fail even if the `mv` get's processed correctly in your above example, because it will try to `mv` it to "/home/foo/some-space-sir/some-file" – Reinstate Monica Please Jan 21 '14 at 05:43
0

Not one line, but avoids sed and should work just as well if you're going to be using it for a script anyway. (replace the mv with an echo if you want to test)

In bash 4+

#!/bin/bash
shopt -s globstar
for file in **/*; do
  filename="${file##*/}"
  if [[ -f $file && $filename == *" "* ]]; then
    onespace=$(echo $filename)
    dir="${file%/*}"
    [[ ! -f "$dir/${onespace// /-}" ]] && mv "$file" "$dir/${onespace// /-}" || echo "$dir/${onespace// /-} already exists, so not moving $file" 1>&2 
  fi
done

Older bash

#!/bin/bash
find . -type f -print0 | while read -r -d '' file; do
  filename="${file##*/}"
  if [[ -f $file && $filename == *" "* ]]; then
    onespace=$(echo $filename)
    dir="${file%/*}"
    [[ ! -f "$dir/${onespace// /-}" ]] && mv "$file" "$dir/${onespace// /-}" || echo "$dir/${onespace// /-} already exists, so not moving $file" 1>&2 
  fi
done

Explanation of algorithm

  • **/* This recursively lists all files in the current directory (** technically does it but /* is added at the end so it doesn't list the directory itself)
  • ${file##*/} Will search for the longest pattern of */ in file and remove it from the string. e.g. /foo/bar/test.txt gets printed as test.txt
  • $(echo $filename) Without quoting echo will truncate spaces to one, making them easier to replace with one - for any number of spaces
  • ${file%/*} Remove everything after and including the last /, e.g. /foo/bar/test.txt prints /foo/bar
  • mv "$file" ${onespace// /-} replace every space in our filename with - (we check if the hyphened version exists before hand and if it does echo that it failed to stderr, note && is processed before || in bash)
  • find . -type f -print0 | while read -r -d '' file This is used to avoid break up strings with spaces in them by setting a delimiter and not processing \

Sample Output

$ tree
.
├── bar
│   ├── some dir
│   │   ├── some-name-without-space1.pdf
│   │   ├── some name with space1.pdf
│   ├── some-name-without-space1.pdf
│   ├── some name with space1.pdf
│   └── some-name-with-space1.pdf
└── space.sh
$ ./space.sh
bar/some-name-with-space1.pdf already exists, so not moving bar/some name with space1.pdf
$ tree
.
├── bar
│   ├── some dir
│   │   ├── some-name-without-space1.pdf
│   │   ├── some-name-with-space1.pdf
│   ├── some-name-without-space1.pdf
│   ├── some name with space1.pdf
│   └── some-name-with-space1.pdf
└── space.sh
Reinstate Monica Please
  • 11,123
  • 3
  • 27
  • 48
0

Another way:

find . -name "* *" -type f |while read file
do
  new=${file// /}
  mv "${file}" $new
done
BMW
  • 42,880
  • 12
  • 99
  • 116
  • This is not very robust if there are other special characters in the file name, and obviously won't work at all if there are names with newlines. – tripleee Jan 21 '14 at 06:52