Makari 21
Makari 21

Reputation: 1

Bash.Bad result of command substitution

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???

Upvotes: 0

Views: 148

Answers (2)

Freddy
Freddy

Reputation: 4688

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

Upvotes: 1

Mahmoud Odeh
Mahmoud Odeh

Reputation: 950

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.

Upvotes: 0

Related Questions