Recursively rename e-mail files from .txt to .eml

Directory "$ d" has a couple of thousand e - mail files with the.txt expansion. To open them in my e - mail customer, I require to rename them to.eml

Will this command rename them appropriately:

find "${d}" -type f -name '*.txt' | while read f; do mv -vn "${f}" "${f%.*}".eml; done

or exists a far better, extra durable means to do this?

I can not assume of a classy means of doing this making use of:

-exec ...{}... \;
2022-06-07 14:32:49
Source Share
Answers: 4

In zsh, zmv makes this easy. Put autoload -U zmv in your ~/.zshrc, then use one of several ways of specifying a replacement text for **/*.txt which matches files with the extension txt in the current directory and subdirectories:

zmv '**/*.txt' '$f:r.eml'
zmv '**/*.txt' '${f%.*}.eml'
zmv '(**/)(*).txt' '$1$2.eml'
zmv -w '**/*.txt' '$1$2.eml'

If you don't have zsh, but you have bash ≥4 or ksh93, you can use the ** to traverse subdirectories recursively and then loop over the matches. You'll need to activate the ** glob pattern first with shopt -s globstar in bash (put it in your ~/.bashrc) or set -o globstar in ksh (put it in your ~/.kshrc). This also works in zsh (no prior setup needed).

for f in **/*.txt; do mv -- "$f" "${f%.*}.eml"; done

Note that this renames all files, not just regular files. If you have directories or other non-regular files and you want to leave them untouched:

zmv -Q '**/*.txt(.)' '$f:r.eml'
for f in **/*.txt; do [[ -f $f ]] && mv -- "$f" "${f%.*}.eml"; done

With no shell feature beyond POSIX, you'll need to invoke find to traverse subdirectories. Your solution is brittle because it breaks on files containing backslashes, trailing whitespace or newlines. You can fix the trailing whitespace and backslashes issue with while IFS= read -r f; do … but piping the output of find inherently breaks on newlines. Use -exec instead. On Linux, you can use the rename utility (whichever your distribution carries). On Debian, Ubuntu and derivatives:

rename 's/\.txt$/.eml/' **/*.txt
find . -name '*.txt' -type f -exec rename 's/\.txt$/.eml/' {} +

On other distributions, as long as none of the file names contain .txt in the middle (because rename substitutes the first occurence of the source string):

rename .txt .eml **/*.txt
find . -name '*.txt' -type f -exec rename .txt .eml {} +

With only POSIX features throughout, invoke a shell to perform the transformation on the name.

find . -name '*.txt' -type f -exec sh -c 'for f; do mv "$f" "${f%.*}.eml"; done' _ {} +

If your find is too old to support -exec … +, you'll need to invoke one shell per file, which makes for simpler but slower code.

find . -name '*.txt' -type f -exec sh -c 'mv "$0" "${0%.*}.eml"; done' {} \;
2022-07-16 12:12:20

I assume you need to be great with

find "$d" -name \*.txt -exec rename .txt .eml {} \;

or perhaps

for f in *.txt; do rename .txt .eml "$f"; done

if all the files remain in the very same directory site.

2022-06-07 14:56:35

Yes, your command will certainly function, thinking you are making use of bash or a covering with comparable syntax. In the future when you are pondering a large command similar to this, bear in mind that you can make use of echo to sneak peek the resulting command lines. I.e. you can place echo before mv, run the pipe and also see what the commands are mosting likely to be. If they look OK, remove echo and also run the command genuine.

2022-06-07 14:56:33

Your remedy is usually alright, yet it will certainly damage on newlines. Below is a somewhat extra durable bash4+remedy:

shopt -s globstar nullglob
for file in **/*.txt; do
    mv "$file" "${file%.*}.eml"
2022-06-07 14:56:23