Workhorse
Workhorse

Reputation: 1560

Replace spaces in files within subdirectories

I have about 10,000 directories containing files. I want to write a loop that iterates through each directory, picks out a .txt file, and replaces any spaces with a _. How can I do this?

for f in *FOLDERS*
do
    cd "$f" && echo "Entering into $f" || { echo "Error: could not enter into $f"; continue; }
    for y in $(ls *.txt | mv "$y" "${y// /_}")
    do
        echo ${y}
    done
done 

But it doesn't work for each directory. What am i doing wrong?

Upvotes: 1

Views: 123

Answers (5)

Ivan
Ivan

Reputation: 7317

This is why your script is not working. Consider this folder structure:

$ ls
d1  d2  d3

Now lets try to cd into each folder:

$ for d in *; { cd $d; pwd; }
/tmp/d1
bash: cd: d2: No such file or directory
/tmp/d1
bash: cd: d3: No such file or directory
/tmp/d1

You have to go back to 'home' folder first:

$ for d in *; { cd $d; pwd; cd ..; }
/tmp/d1
/tmp/d2
/tmp/d3

Or use full path in $d definition:

$ for d in $PWD/*; { cd $d; pwd; }
/tmp/d1
/tmp/d2
/tmp/d3

Upvotes: 1

petrus4
petrus4

Reputation: 614

For something fast, you will probably want Sorpigal's answer; this will still work, but it's slower.

#!/bin/sh -x

find . -type f -name '* *.txt' > stack

next () {
[[ -s stack ]] && main
end
}

main () { 
line=$(sed -n "1p" stack)
echo "${line}" | tr '/' '\n' > f2
basename=$(sed -n "$p" f2)
sed -i "$d" f2
dirname=$(cat f2 | tr '\n' '/')
newname=$(echo "${basename}" | tr ' ' '_')
mv -v "${dirname}/${basename}" "${dirname}/${newname}"
sed -i "1d" stack
rm -v ./f2
next
}

end () {
rm -v ./stack
exit 0
}

next

Upvotes: 1

M. Nejat Aydin
M. Nejat Aydin

Reputation: 10133

This might be what you're trying to do:

#!/bin/bash

for d in */; do
    cd "$d" || exit
    for t in *\ *.txt; do
        [[ -f $t ]] && mv -i "$t" "${t// /_}"
    done
    cd ..
done

Or, if you want to do it recursively (for all subdirectories in any depth):

#!/bin/bash

shopt -s globstar

for d in **/; do
    cd "$d" || exit
    for t in *\ *.txt; do
        [[ -f $t ]] && mv -i "$t" "${t// /_}"
    done
    cd - >/dev/null
done

Upvotes: 3

sorpigal
sorpigal

Reputation: 26106

Select files with find, then run bash to perform the filename manipulation.

find . -type f -name '* *.txt' -exec bash -c 'for path; do
    basename="${path##*/}"
    dir="${path%/*}"
    echo mv "$path" "$dir"/"${basename// /_}"
done'  - {} +

This is fully recursive; if you don't want that you can limit the selection depth with find. The other advantage to using find is you can tell for sure what files you will be operating on before you run the dangerous part.

The above is harmless as-is, to make it actually perform its function remove the echo before the mv.

Upvotes: 3

KamilCuk
KamilCuk

Reputation: 141900

  1. List all files with spaces in filenames.
  2. In that list, duplicate each line. In the second line, change all spaces in the filename for _.
  3. For every two lines, execute mv.

find *FOLDERS* -type f -name '* *.txt' -print0 |
# Duplicate the line. Replace spaces by _ in the second line.
sed -Ez 'h ; s@.*/@@ ; s@ @_@g ; G ; s@([^\x00]*)\x00(.*/)?([^/]*)@\2\3\x00\2\1@' |
# Execute mv for each two arguments.
xargs -0 -n2 echo mv -v

Upvotes: 3

Related Questions