0

I found this answer to my question which uses the following code to remove words from file names using sed:

for fyle in $(find . -name "*.*")
do 
  mv -i $fyle `echo $fyle | sed -e 's/FOO//gI' -e 's/BANG//gI' `
done

But on my mac it chokes on filenames whose paths have spaces.

I tried to fix it by use of double quotes, but couldn't get it to work: the variable fyle now includes the entire list of files, not one at a time.

Because the original poster seemed happy with the code, maybe my problem is because of my OSX flavour of bash?

How can I modify the code above to work well?

Community
  • 1
  • 1
Tim
  • 291
  • 2
  • 17

4 Answers4

4

You should not iterate the find command's output in for loop otherwise shell expansion for filenames with space/newline will occur and your results will be wrong.

Use -print0 option in find and iterate using process substitution in a while read loop with null IFS.

while IFS= read -rd '' fyle; do
   mv -i "$fyle" $(sed 's/FOO//gI; s/BANG//gI' <<< "$fyle")
done < <(find . -type f -name "*.*")
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • This works well, i preferred the answer of @ghoti because of simplicity; i am a newbie. – Tim Oct 31 '15 at 13:49
  • hold that comment, i just noticed that even though I didn't get an error, the file names have all remained unchanged. So it didn't work! Any ideas? – Tim Oct 31 '15 at 14:06
  • Insert a `declare -p fyle;` line before `mv` line and tell me what it prints. – anubhava Oct 31 '15 at 19:19
  • it prints nothing! but don't bother with this anymore, I've discovered the problem [here](http://askubuntu.com/questions/343727/filenames-with-spaces-breaking-for-loop-find-command) and I'm using the globstar approach. – Tim Oct 31 '15 at 19:50
  • Strange so you mean that `find` is not finding those files but `globstar` is able to find them? – anubhava Nov 01 '15 at 10:32
4

As others have said, quotes are your friends.

But since you're using bash, you don't really need to use sed just to remove strings from filenames. Something like this might work, using parameter expansion:

find . -name '*.*' -exec bash -c 'out="${1//FOO/}; out="${out//BANG/}; mv -v "$1" "$out"' -- {} \;

But even find is unnecessary in this case, so you can avoid subshells entirely:

shopt -s globstar
for f in **/*FOO*.* **/*BANG*.*; do
  out=${f//FOO/}
  out=${out//BANG/}
  mv -v "$f" "$out"
done

This of course assumes that the keywords you're searching for are before a dot in the filename. It also assumes that you're running bash v4, for globstar.

Community
  • 1
  • 1
ghoti
  • 45,319
  • 8
  • 65
  • 104
  • Ah, thanks muchly. :-) A combo of copy-paste and too-much-PHP-today I think. – ghoti Oct 31 '15 at 03:13
  • Unfortunately the BASH in Mac OSX gives me an error on the first line. I suspect it is because the BASH shipped with OSX is version 3. – Tim Oct 31 '15 at 14:07
  • Is there any way I can modify my original code to produce a solution, without depending on globstar? – Tim Oct 31 '15 at 14:15
  • @Tim, Right, I should have mentioned that earlier. Added the bash 4 dependency to the answer. As for how else to do this .. well, globstar is the only native bash way I know to recurse through directories the way `find` does. You have many choices -- find, bash4+globstar via [macports](https://www.macports.org/)/[brew](http://brew.sh/)/etc, PHP's [RecursiveDirectoryIterator class](http://php.net/manual/en/class.recursivedirectoryiterator.php) (since PHP 5 is included in OSX), Python's [os.walk](http://stackoverflow.com/a/2212698/1072112) (since Python is also included). Pick your poison. :) – ghoti Oct 31 '15 at 14:30
  • It's not too difficult to write a recursive shell function to do recursive directory descent in `bash` 3, but it's probably easier to simply install `bash` 4 via something like Homebrew. – chepner Oct 31 '15 at 15:16
  • yep, love globstar now. also see [here](http://askubuntu.com/questions/343727/filenames-with-spaces-breaking-for-loop-find-command) for why my original approach cannot be fixed. and they suggested the same approach as @anubhava. – Tim Oct 31 '15 at 19:52
2

I believe you need to double-quote the variable reference in the mv command to account for the spaces...

mv -i "$fyle" ...
macgregor
  • 147
  • 8
0

That seems very complicated. Will this work? Uses Bash's built-in string substitution and file globbing to replace bar with baz:

for i in *.txt; do mv "$i" "${i/bar/baz}"; done

Here it is in action:

mike ~/foobar $ touch "foo bar baz.txt"
mike ~/foobar $ touch "foobar.txt"
mike ~/foobar $ touch "carbar.txt"
mike ~/foobar $ for i in *.txt; do mv "$i" "${i/bar/baz}"; done
mike ~/foobar $ ls
carbaz.txt      foo baz baz.txt foobaz.txt
mike ~/foobar $

As chepner mentioned below, this will not recurse into directories.

miken32
  • 42,008
  • 16
  • 111
  • 154
  • Too simple; you aren't replicating the full behavior of `find`, and there is more than one substitution to make in the file name. – chepner Oct 30 '15 at 17:11
  • `find . -name "*.*"` isn't very complicated! Though I guess there is no recursion... – miken32 Oct 30 '15 at 17:12