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:
- List all files with spaces in filenames.
- In that list, duplicate each line. In the second line, change all spaces in the filename for
_
. - 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