I'm trying to change the name of multiple directories using bash, the names being structures like the following:
DRMAD_CA-12__MRBK01_237a8430 DRMAD_CA-17__MRBK10_766c3396
DRMAD_CA-103__MRBK100_c27a6c1c
The goal is the to keep the MRBK as well as the number following directly after it (MRBK###), but to get rid of of the rest. The pattern of the prefix is always the same (DRMAD_CA-###__), while the suffix is '_' followed by a combination of exactly 8 letters and digits. Tried sed, but can't seem to figure out the right pattern.
Seeing other posts on Stackoverflow, I've tired variations of
ls | while read file; do new=$( echo $file | sed 's/[^0-9]*\([^ ]*\)[^.]*\(\..*\)*MRBK\1\2/' ) mv "$file" "$new" done
But since I don't really understand the syntax of sed, it doesn't produce a usable result.
Use rename utility.
First, print the old and new names, but do not rename anything:
rename --dry-run 's/.*(MRBK\d+).*/$1/' *MRBK*
If OK, actually rename:
rename 's/.*(MRBK\d+).*/$1/' *MRBK*
Install rename, for example, using conda.
Using find:
find . -type d -regextype posix-extended -regex "^.*MRBK[[:digit:]]+.*$" | while read line
do
dir=$(dirname $line)
newfil=$(grep -Eo 'MRBK[[:digit:]]+' <<< $line)
mv "$line" "$dir/$newfil"
done
I am working on a shell script, and now I came to a point where I need to rename files that start with a number and a blank by removing this pattern and moving them to a specific folder that is basically the string between the second and third " - "
example :
001 - folder1 - example.doc > /folder1/example.doc
002 - folder2 - someexample.doc > /folder2/someexample.doc
003 - folder3 - someotherexample.doc > /folder3/someotherexample.doc
I want to do something like
find /tmp -name '*.doc' -print | rename .... ...
what I do not know is:
- how to tell find that the file starts with a number,
and second
- how to explode the name by a pattern like " - " and tell rename to place the file in the folder
If possible, I would avoid find and just use bash's regular expression matching. If you don't need to recursively search /tmp (as your version of find is doing), this should work in any version of bash:
regex='^[[:digit:]]+ - (.+) - (.+)$'
for f in /tmp/*.doc; do
[[ $f =~ /tmp/$regex ]] || continue
mv -- "$f" "/${BASH_REMATCH[1]/${BASH_REMATCH[2]}"
done
If you do need to recursively search, and you are using bash 4 or later, you can use the globstar option:
shopt -s globstar
regex='^[[:digit:]]+ - (.+) - (.+)$'
for f in /tmp/**/*.doc; do
b=$(basename "$f")
[[ $b =~ $regex ]] || continue
mv -- "$f" "/${BASH_REMATCH[1]/${BASH_REMATCH[2]}"
done
If your destination folder and file names have no spaces, and if all your original files are in the current directory, you could try something like:
while read f; do
if [[ "$f" =~ ^\./[0-9]+\ -\ ([^\ ]+)\ -\ (.+\.doc)$ ]]; then
mkdir -p "${BASH_REMATCH[1]}"
mv "$f" "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
fi
done < <(find . -maxdepth 1 -name '[0-9]*.doc')
Explanations:
find . -maxdepth 1... restricts the search to the current directory.
find...-name '[0-9]*.doc matches only files which names start with at least one digit and end with .doc.
The regular expression models your original file names (with the initial ./ that find adds). It contains two sub-expressions enclosed in (...) corresponding to the folder name and to the destination file name. These sub-expressions are stored by bash in the BASH_REMATCH array if there is a match, at positions 1 and 2, respectively.
The regular expression removes leading and trailing spaces from the destination folder name and the leading spaces from the destination file name (I assume this is what you want).
With gawk:
find . -regex '^.*[0-9]+ - [a-zA-Z]+[0-9] - [a-zA-Z]+\.doc$' -printf %f | awk -F- '{ print "mv \""$0"\" /"gensub(" ","","g",$2)"/"gensub(" ","","g",$3) }'
Use find with regular expressions and then parse the output through to awk to execute the move command.
When you have verified that the commands are OK, run the commands with.
find . -regex '^.*[0-9]+ - [a-zA-Z]+[0-9] - [a-zA-Z]+\.doc$' -printf %f | awk -F- '{ print "mv \""$0"\" /"gensub(" ","","g",$2)"/"gensub(" ","","g",$3) }' | sh
Be wary that such a strategy can be open to a risk of command injection.
the answer is very close to the suggestion made by Raman, I removed printf, added the part that creates the folder and added the part removing the leading and the trailing blank.
in the end it looks like this:
find . -regex '^.*[0-9].*\.doc$' | awk -F- '{ print "mkdir \""gensub(" $","","g",gensub("^ ","","g",$2))"\" \nmv \""$f"\" \"./"gensub(" $","","g",gensub("^ ","","g",$2))"/"gensub(" $","","g",gensub("^ ","","g",$2"-"$3))"\"" }'|sh
thank you everybody for the suggestions.
I'm able to find all files in a directory and its subdirectories. I save it into an array but I also need to rename ones that have spaces in their names to an underscore.
Sample structure
./abc 123.txt
./dags/ftp.pyc
./dags/ftp.py
./logs/scheduler/2017-05-12/ftp.py.log
Find the files and insert into array
array=($(find . -type f -print0 | xargs -0))
# Does not work
for i in ${array[#]};do echo ${i// /_};done
#Output
./abc
123.txt
./dags/ftp.pyc
./dags/ftp.py
./logs/scheduler/2017-05-12/ftp.py.log
It would be more ideal if I could run regex against the value before it goes into the array.
The problem with the above command is that the array is looping through the filename with space as two variables and not one.
Something like below should work
find . -type f -print0 | while IFS= read -r -d '' file; do echo ${file// /_} ; done
The following scripts finds all files in the current directory (recursively) and replaces dash - characters to underscore chars in file names
find . -type f -name '*.png' | while read FILE ; do
newfile="$(echo ${FILE} |sed -e 's/-/_/g')";
mv "${FILE}" "${newfile}" ;
done
given a file in the path that contains dashes, for example, drawable-hdpi/file-name.png, the script will try to rename it to drawable_hdpi/file_name.png - replacing the dash in the directory name as well as in the file name.
I would like to avoid modifying the directory path, and only rewrite the file name.
Any suggestions on how to modify the sed usage to skip the directory path?
The shell has some nifty string operators to chop parts of variables. Your shell manual page has all the details. Here's how I would use them:
find . -type f -name '*.png' |
while read FILE; do
dir=${FILE%/*}
newfile=$(echo "${FILE##*/}" |sed -e 's/-/_/g')
mv "${FILE}" "${dir}/${newfile}"
done
Explanation:
dir=${FILE%/*} chops the shortest part from the right that matches the /* glob, removing the slash and file name, giving the directory.
${FILE##*/} removes the longest part from the left matching */, i.e. leaves just the file name.
Pulling my hair out - somebody save me from an early Q-ball.
I have a folder with loads of powerpoint files and I want to change a substring in each title. All of them are of the form "lecture 2 2014.pptx" and I want to change "2014" to "2016".
Insider the directory I try commands like:
find . -name "*2014*" | xargs -0 sed -i 's/2014/2016/g'
to no avail. Any advice?
Edit my goal is to change the file name. "Lecture 2 2014.pptx" to "Lecture 2 2016.pptx"
rename s/2014/2016/ *2014.pptx
If your list is too long to expand by shell try:
find -name \*2014.pptx -exec rename s/2014/2016/ {} \;
rename was already mentioned. Be aware that there are two version floating around: one with the syntax
rename [options] expression replacement file...
and one with the syntax
rename s/old/new/ file...
As an alternative: a simple Bash loop with a regex extracting the "2014" from each file name, replacing it with "2016"
re='(.*)2014(.*)'
for fname in *2014*; do
[[ $fname =~ $re ]]
mv "$fname" "${BASH_REMATCH[1]}2016${BASH_REMATCH[2]}"
done