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

Directory “$d” contains a few thousand e-mail files with the .txt extension. To open them in my e-mail client, I need to rename them to .eml

Will this command rename them correctly:

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

or is there a better, more robust way to do this?

I could not think of an elegant way of doing this using:

-exec ...{}... ;

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Your solution is generally ok, but it will break on newlines. Here is a slightly more robust bash4+ solution:

shopt -s globstar nullglob
for file in **/*.txt; do
    mv "$file" "${file%.*}.eml"
done

Method 2

I think you should be fine with

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

or even

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

if all the files are in the same directory.

Method 3

Yes, your command will work, assuming you’re using bash or a shell with similar syntax. In the future when you’re contemplating a big command like this, remember that you can use echo to preview the resulting command lines. I.e. you could put echo in front of mv, run the pipeline and see what the commands are going to be. If they look OK, remove echo and run the command for real.

Method 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' {} ;


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x