Home > OS >  Replace spaces in files within subdirectories
Replace spaces in files within subdirectories

Time:09-30

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?

CodePudding user response:

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

CodePudding user response:

  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

CodePudding user response:

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.

CodePudding user response:

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

CodePudding user response:

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
  • Related