0

I want to replace spaces in filenames. My test directory contains files with spaces:

$ ls
'1 2 3.txt'  '4 5.txt'  '6 7 8 9.txt'

For example this code works fine:

$ printf "$(printf 'spaces in file name.txt' | sed 's/ /_/g')"
spaces_in_file_name.txt

I replace spaces on underscore and command substitution return result to double quotes as text. This construction with important substitution is essential in the next case. Such commands as find and xargs have substitution mark like {}(curly braces). Therefore the next command can replace spaces in files.

$ find ./ -name "*.txt" -print0 | xargs --null -I '{}' mv '{}' "$( printf '{}' | sed 's/ /_/g' )"
mv: './6 7 8 9.txt' and './6 7 8 9.txt' are the same file
mv: './4 5.txt' and './4 5.txt' are the same file
mv: './1 2 3.txt' and './1 2 3.txt' are the same file

But I get error. In order to more clearly consider error, instead of mv I just use echo(or printf):

$ find ./ -name "*.txt" -print0 | xargs --null -I '{}' echo "$( printf '{}' | sed 's/ /_/g' )"
./6 7 8 9.txt
./4 5.txt
./1 2 3.txt

As we can see, spaces were not replaced on underscore. But without command substitution, the replacing will be correct:

$ find ./ -name "*.txt" -print0 | xargs --null -I '{}' printf '{}\n' | sed 's/ /_/g'
./6_7_8_9.txt
./4_5.txt
./1_2_3.txt

So the fact of the command substitution with curly braces is corrupt the result(because in the first command was correct result), but without command substitution the result is correct. But why???

Makari 21
  • 1
  • 1
  • There's an unsatisfying duplicate [here](https://stackoverflow.com/questions/22589438/xargs-command-substitution-with-pipe-doesnt-work/22589627), and a cross-site answer [here](https://superuser.com/questions/1217376/why-doesnt-command-substitution-work-inside-find) which is about 'find' more than xargs, but explains the problem properly – that other guy May 14 '20 at 18:16

2 Answers2

1

Your command substitution is run before find and you're executing

mv '{}' "{}"

You could change the find command to match .txt files with at least one space character and use -exec and a small bash script to rename the files:

find . -type f -name "* *.txt" -exec bash -c '
  for file; do
    fname=${file##*/}
    mv -i "$file" "${file%/*}/${fname// /_}"
  done
' bash {} +
  • ${file##*/} remove the parent directories (longest prefix pattern */) and leaves the filename (like the basename command)
  • ${file%/*} removes the filename (shortest suffix pattern /*) and leaves the parent directories (like the dirname command)
  • ${fname// /_} replaces all spaces with underscores
Freddy
  • 4,548
  • 1
  • 7
  • 17
  • i can see any value of using `find` you can mv the file to `"${file// /_}"` – Mahmoud Odeh May 14 '20 at 18:56
  • 1
    The value of `find` is that it searches in all subdirectories and using the pattern `* *.txt` only files containing (at least) one space character are renamed. Your answer makes sense if only one directory is processed. – Freddy May 14 '20 at 19:09
0

it's quite fast and simple with loop just replace absolute_path with your path :

for f in absolute_path/*.txt; do mv "$f" "${f// /_}";done

The ${f// /_} part utilizes bash's parameter expansion mechanism to replace a pattern within a parameter with supplied string.

Mahmoud Odeh
  • 942
  • 1
  • 7
  • 19