batch rename file extensions in subdirectories - shell

I'm trying to create a batch file in linux that will allow me to change extensions of files in multiple subdirectories. After much searching and experimenting i've found what seems to be a solution:
find /volume1/uploads -name "*.mkv" -exec rename .mkv .avi {} +
When running the script i get the following error:
find: -exec CMD must end by ';'
I've tried adding ; and \; (with or without +) but to no avail. What's wrong with the command and how can I fix it?
Edit: Running on a Synology NAS with DSM 4.2

you have to escape all characters that would be interpreted by bash. in your case these are the semicolon and the curly braces (you forgot to escape the latter in your code):
find /volume1/uploads -name "*.mkv" -exec rename .mkv .avi \{\} \;
the {} (in our case \{\}) is expanded to the filename, so the actual call would look like rename .mkv .avi /volume1/uploads/foo/bla.mkv (which is not the exact syntax the /usr/bin/rename needs, at least on my system).
instead it would be something like:
find /volume1/uploads -name "*.mkv" -exec rename 's/\.mkv$/.avi/' \{\} \;
UPDATE
if you don't want to (or cannot) use perl's rename script, you could use the following simple bash script and save it as /tmp/rename.sh
#!/bin/sh
INFILE=$1
OUTFILE="${INFILE%.mkv}.avi"
echo "moving ${INFILE} to ${OUTFILE}"
mv "${INFILE}" "${OUTFILE}"
make it executable (chmod u+x /tmp/rename.sh) and call:
find /volume1/uploads -name "*.mkv" -exec /tmp/rename.sh \{\} \;
UPDATE2
it turned out that this question is really not about bash but about busybox.
with a limited shell interpreter like busybox, the simplest solution is just to append the new file extension:
find /volume1/uploads -name "*.mkv" -exec mv \{\} \{\}.avi \;

Not sure how different find and rename commands are on your DSM 4.2 OS so try something like:
find /volume1/uploads -name "*.mkv" | while read filename;
do mv -v "${filename}" "$(echo "${filename}" | sed -e 's/\.mkv$/\.avi/')"
done

Related

Script that replaces all "*.html" file names with "*.html.<username>"

Write a script that replaces all *.html file names with *.html.<username> in the current directory.
You could use find's -exec option:
$ find . -name "*.html" -exec mv '{}' '{}'.${USER} \;
If you have perl-rename installed (called rename on Debian-based and many other systems, /usr/bin/site_perl/rename on others), you can run:
rename "s/\.html$/.html.$USER/" *
If you don't have the perl rename, but have the compiled one, you can do:
rename '.html' ".html.$USER" *
If you have neither, use the shell:
for f in *.html; do mv "$f" "$f"."$USER"; done

How to delete a file except in any one of the subdirectory using shell script

Hi I want to delete a file from any of the subdirectories except one of the subdirectory.
For ex
folder a->a.txt
folder b->subdir 1 -> msgdir-> a.txt
folder c->
Now i want to delete a.txt only in folder a but not the file in msgdir .msgdir can be in any level of subdirectories as it would be changing.
Please help me to resolve this.
This will ignore specifically the msgdir at any level and remove a.txt except in msgdir.
find . ! -path '*/msgdir/*' -name a.txt -type f -delete
Tested with GNU find 4.4.2 and BSD find (Mac Yosemite).
The following approach is overkill if you have GNU find (or a newer BSD one), with the -path option. Otherwise, read on...
You haven't specified which shell you're using but if you have bash, you could go with something like this:
find -name a.txt -exec bash -c "[[ '{}' != */msgdir/* ]]" \; -print
This filters out paths containing /msgdir/, as the test will only pass if the file path doesn't contain the string. If you're happy with the results, you can change -print to -delete.
Without bash, you could use grep to determine the match:
find -name a.txt -exec sh -c "printf '%s' '{}' | grep -qv '/msgdir/'" \; -print

In unix , moving a file with xargs after finding and zipping it?

So in a bashscript i've the following very simple line , but how can i chain it further to move the file ?
find . -type f -ctime -$2 -name "mylog*.log" | xargs bzip2
This works fine but i'd also like to move the file to a new directory once I am done with the bzip2.
One standard trick is to use a new script that does whatever you need. Here, I assume that ${OTHER_DIRECTORY} is an environment variable that says where to put the compressed files, but there are plenty of other (better!) ways to get that information to the script (such as specifying the directory as the first argument — as the last argument is a bad idea).
#!/bin/bash
for file in "$#"
do
bzip2 "$file"
mv "$file.bz2" "${OTHER_DIRECTORY:-/tmp}"
done
You then run that script with find:
find . -type f ctime -$2 -name "mylog*.log" -exec tinyscript.sh {} +
This is pretty effective. If you only want one mv command, you can consider something along the lines of:
bzip2 "$#"
bz2=()
for file in "$#"; do bz2+=( "$file.bz2" ) done
mv "${bz3[#]}" "${OTHER_DIRECTORY:-/tmp}"
This code works even if the path names contain spaces and other untoward characters.
One option might be something like this:
find . -type f -ctime -$2 -name "mylog*.log" -exec bzip2 {} \; -exec mv {} /path/to/new_dir/ \;

Rename files with dynamic name in bash

I am trying to rename all files which contains 'Name' with dynamic name.
So this file 'NameSomething' should look like this 'SearchSomething'. But script below just removes 'Name' from file name.
name='search'
Name='Search'
find ../../$name-module -name 'Name*' -type f -exec bash -c 'mv "$1" "${1/Name/$Name}"' -- {} \;
You have to export Name, because otherwise the command find will not inherit the variable $Name into its environment. So:
name='search'
export Name='Search'
find ../../$name-module -name 'Name*' -type f -exec bash -c 'mv "$1" "${1/Name/$Name}"' -- {} \;
\You can escape quotes, no need for an export:
find ../../$name-module -name 'Name*' -type f -exec bash -c "mv \"\$1\" \"\${1/Name/$Name}\"" -- {} \;
You might use find to find a list of the files you want to rename and pipe that to another program to do the renaming.
For example, you could do:
find ../../$name-module -print0 -name 'Name*' -type f | \
xargs -0 rename "s/Name/$Name/"
No need for exporting (ugly + might affect other commands)
Rename might get run only once (if all the found files fit on one command line) (with -exec, you're running two programs -- bash and mv -- per each file. That might be inefficient.
(You could get further performance increase with this approach by using Gnu parallel instead of xargs, if you're on a multicore computer)
You could use the Perl-based rename command:
Name=Search
rename "s/^Name/$Name/" *

issue with changing file extensions with bash

find /path/to/files -type f -not -name "M*'.jpg" -exec mv "{}" "{}".mxg \;
I fear I made two mistakes.
Files are stored in a directory structure. Goal is to keep the filenames and change the file extension from .jpg to .mxg. But only for files that have 'M' as the first character of there filename.
The above line has this result:
all files have .mxg added. So the .jpg isn't and all files are changed.
This should do it:
find /path/to/files -type f -name 'M*.jpg' -exec bash -c 'echo mv "$1" "${1/jpg/mxg}"' -- {} \;
A somewhat cleaner solution is if you have the rename command. However, there are different implementations out there, so read your man page first to check you have the same as mine. The version I have in Debian is Larry Wall's implementation in perl. You can recognize this by the example rename 's/\.bak$//' *.bak near the top, or the AUTHOR section near the bottom. With this implementation you can rename your files like this:
find /path/to/files -type f -name 'M*.jpg' -exec rename 's/jpg$/mxg/' {} \;

Resources