Bash script to rename file names with correct date format in all sub folders in Linux - linux

I have a buch of logs with names in "filename.logdate month year" (for example, filename.log25 Aug 2015, note there are space between the date/month/year) and I'd like to change them to "filename.logmonthdateyear" (for example filename.logOct052015, with no space).
These files are in a bunch of sub folders which makes it more challenging.
Parent Folder
--- sub folder1
file1
file2
--- sub folder2
file3
file4
etc.
Can anyone suggest a bash script that can do this?
Thank you!

find and rename should do the trick
strawman example:
to go from
...
├── foo/
│   ├── file name with spaces
│   └── bar/
│   └── another file with spaces
...
you can use
find foo/ -type f -exec rename 's/ //g' '{}' \;
to get
...
├── foo/
│ ├── filenamewithspaces
│ └── bar/
│ └── anotherfilewithspaces
...
in your case:
in your case, it would be something like
find path/to/files/ -type f -exec rename 's/ //g' '{}' \;
but you can use fancier filters in your find command like
find path/to/files/ -type f -name *.log* -exec rename 's/ //g' '{}' \;
to select only .log files in case there are other file names with spaces you don't want to touch
heads up:
as pointed out in the comments there's the potential to overwrite files if their names only differ by space placement (e.g., a bc.log and ab c.log if carelessly renamed would end up with a single abc.log).
for your case, you have two things on your side:
rename will give you a heads up as long as you're not using it's --force option
and will give you a helpful message like ./ab c.log not renamed: ./abc.log already exists
your files are named programatically, and you're stripping the spaces in dates, so, assuming that's all you have in there, you shouldn't have any problems
regardless, it's good to be mindful of this sort of thing

This is a way to do it with just Bash (4+) and 'mv':
# Prevent breakages when nothing matches patterns
shopt -s nullglob
# Enable '**' matches (requires Bash 4)
shopt -s globstar
topdir=$PWD
for folder in **/ ; do
# Work in the directory to avoid problems if its path has spaces
cd -- "$folder"
for file in *' '*' '* ; do
# Use the '-i' option to prevent silent clobbering
mv -i -- "$file" "${file// /}"
done
cd -- "$topdir"
done
If there is just one level of subfolders (as stated in the question), the requirement for Bash 4+ can be dropped: remove the shopts -s globstar, and change the first line of the outer loop to for folder in */ ; do.

Related

find files based on extension but display name without extension no basename, sed, awk, grep or ; allowed

I need to write a script that lists all the files with a .gif extension in the current directory and all its sub-directories BUT DO NOT use ANY of:
basename
grep
egrep
fgrep
rgrep
&&
||
;
sed
awk
AND still include hidden files.
I tried find . -type f -name '*.gif' -printf '%f\n' which will succesfully display .gif files, but still shows extension. Here's the catch: if I try to use cut -d . -f 1 to remove file extension, I also remove hidden files (which I don't want to) because their names start with ".".
Then I turned to use tr -d '.gif' but some of the files have a 'g' or a '.' in their name.
I also tried to use some of these answers BUT all of them include either basename, sed, awk or use some ";" in their script.
With so many restrictions I really don't know if it's even possible to achieve that but I'm supposed to.
How would you do it?
files/dirs structure:
$ tree -a
.
├── bar
├── bar.gif
├── base
│   └── foo.gif
├── foo
│   └── aaa.gif
└── .qux.gif
3 directories, 4 files
Code
find -type f -name '*.gif' -exec bash -c 'printf "%s\n" "${#%.gif}"' bash {} +
Output
./bar
./.qux
./foo/aaa
./base/foo
Explanations
Parameter Expansion expands parameters: $foo, $1. You can use it to perform string or array operations: "${file%.mp3}", "${0##*/}", "${files[#]: -4}". They should always be quoted. See: http://mywiki.wooledge.org/BashFAQ/073 and "Parameter Expansion" in man bash. Also see http://wiki.bash-hackers.org/syntax/pe.
Something like:
find . -name '*.gif' -type f -execdir bash -c 'printf "%s\n" "${#%.*}"' bash {} +
Using perl:
perl -MFile::Find::Rule -E '
say s/\.gif$//r for File::Find::Rule
->file()
->name(qr/\.gif\z/)
->in(".")
'
Output:
bar
.qux
foo/aaa
base/foo

BEST Linux script; to rename SRT to name of movie file in same folder; multiple sub folders [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
There have been multiple attempts to answer this question, but no correct script can be found.
The problem:
SRT subtitles will not load unless having the same name as the movie, or same name as movie +.en.srt or .es.srt or .fr.srt and so on.
1000's of movie directories within a main movie directory having within their respective movie directory, sometimes 1+ .srt files (1_English.srt, 2_English.srt, *French.srt, etc.).
My media server is using Ubuntu, so the solution should use a BASH script.
Here is a snippet of my file structure:
Test-dir$ tree
.
├── renamer.sh
├── Saga.of.the.Phoenix.1990.1080p
│   ├── 1_French.srt
│   ├── 1_Spanish.srt
│   ├── 2_English.srt
│   ├── 3_English.srt
│   └── Saga.of.the.Phoenix.1990.1080p.BluRay.x265.mp4
├── Salt.and.Pepper.1968.1080p
│   ├── 1_French.srt
│   ├── 1_Spanish.srt
│   ├── 2_English.srt
│   ├── 4_English.srt
│   └── Salt.and.Pepper.1968.1080p.mp4
└── Salyut-7.2017.1080p.BluRay.x265
├── 2_English.srt
└── Salyut-7.2017.1080p.BluRay.x265.mp4
The questions:
In writing a BASH script,
There are multiple srt files with the same language, I usually like to choose the bigger file and remove the smaller file, the first part of script would have to sort same language srt and delete the smaller ones, how to script this?
How to change the name of srt's to have the same name as the movie file (not always mp4, sometimes mkv or avi.), while appending acronyms for language (en, es, fr, ru,..) if English.srt then change name to "MovieName".en.srt?
I have started the script removing srt files from the SUB directories of the movie directory and then deleting the SUB directory.
Also, added a script to delete any unwanted parts in the string of the movie, or delete unwanted files.
#!/bin/bash/
# Using current working DIR of where script is ran from
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# Moves srt files from SUB folders to their movie folder.
for i in */Subs; do
mv "$i"/* "$i"/..
done
# Removes Subs directory.
find $DIR/* -type d -name "Subs" -exec rm -rf {} +
# Removing the additional rar string from the folders and their movie names.
find . -depth -name '*-rar*' -execdir bash -c 'for f; do mv -i "$f" "${f//-rar/}"; done' bash {} +
# Removing unwanted files from all movie folders.
find . -maxdepth 2 -type f \( -name "RAR.txt" -o -name "RAR.nfo" \) -delete
######## Your helper code starts from here to answer questions 1 and 2 #####################
Many thanks for helping with this conundrum, not only will this help one person, but many, on our quest to free many hours of copying, deleting, pasting, and all with a single script.
Update:
BTB91 gave a brilliant answer and has worked, however to help others learn the many ways to go about solving the same problem above I would like to keep this thread open.
IFS=$'\n' eval "MOVS=(\$( find \"\$DIR\" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' ))" # list of movies
for M in "${MOVS[#]}" ; do
cd "$DIR/$M"
IFS=$'\n' eval "LANGS=(\$( ls | sed -nr 's/.*_([[:alpha:]]+).srt/\1/p' | sort -u ))" # list of languages for movie
for L in "${LANGS[#]}" ; do
IFS=$'\n' eval "FILES=(\$( ls -S *_$L.srt))" # list files for language sorted by size
case "${L,,}" in
en*)
L=en
;;
sp*|es*)
L=es
;;
esac
mv -v "${FILES[0]}" "$M.$L.srt"
FILES[0]=
rm -vf "${FILES[#]}"
done
cd "$OLDPWD"
done
I used "IFS=$'\n' eval ..." because the directory or file names might contain spaces.

Linux find files and folders based on name length but output full path

I have the following folder structure:
├── longdirectorywithsillylengththatyouwouldntnormallyhave
│   ├── asdasdads9ads9asd9asd89asdh9asd9asdh9asd
│   └── sinlf
└── shrtdir
├── nowthisisalongfile0000000000000000000000000
└── sfile
I need to find files and folders where their names length is longer is than x characters. I have been able to achieve this with:
find . -exec basename '{}' ';' | egrep '^.{20,}$'
longdirectorywithsillylengththatyouwouldntnormallyhave
asdasdads9ads9asd9asd89asdh9asd9asdh9asd
nowthisisalongfile0000000000000000000000000
However, This only outputs the name of the file or folder in question. How can I output the full path of resulting matches like this:
/home/user/Desktop/longdirectorywithsillylengththatyouwouldntnormallyhave
/home/user/Desktop/longdirectorywithsillylengththatyouwouldntnormallyhave/asdasdads9ads9asd9asd89asdh9asd9asdh9asd
/home/user/Desktop/shrtdir/nowthisisalongfile0000000000000000000000000
If you use basename on your files, you lose the information about what file you are actually handling.
Therefore you have to change your regex to be able to recognize the length of the last path component.
The simplest way I could think of, would be:
find . | egrep '[^/]{20,}$' | xargs readlink -f
This makes use of the fact, that filenames cannot contain slashes.
As the result then contains path relative to you current cwd, readlink to can be used to give you the full path.
I cant test it right now but this should do the job:
find $(pwd) -exec basename '{}' ';' | egrep '^.{20,}$'
find -name "????????????????????*" -printf "$PWD/%P\n"
The -printf option of find is very mighty. %P:
%P File's name with the name of the starting-point under which it was found removed. (%p starts with ./).
So we add $PWD/ in front.
/home/stefan/proj/mini/forum/tmp/Mo/shrtdir/nowthisisalongfile0000000000000000000000000
/home/stefan/proj/mini/forum/tmp/Mo/longdirectorywithsillylengththatyouwouldntnormallyhave
/home/stefan/proj/mini/forum/tmp/Mo/longdirectorywithsillylengththatyouwouldntnormallyhave/asdasdads9ads9asd9asd89asdh9asd9asdh9asd
To prevent us from manually counting question marks, we use:
for i in {1..20}; do echo -n "?" ; done; echo
????????????????????

How to list directories and files in a Bash by script?

I would like to list directory tree, but I have to write script for it and as parameter script should take path to base directory. Listing should start from this base directory.
The output should look like this:
Directory: ./a
File: ./a/A
Directory: ./a/aa
File: ./a/aa/AA
Directory: ./a/ab
File: ./a/ab/AB
So I need to print path from the base directory for every directory and file in this base directory.
UPDATED
Running the script I should type in the terminal this: ".\test.sh /home/usr/Desktop/myDirectory" or ".\test.sh myDirectory" - since I run the test.sh from the Desktop level.
And right now the script should be run from the level of /home/usr/Dekstop/myDirectory"
I have the following command in my test.sh file:
find . | sed -e "s/[^-][^\/]*\// |/g"
But It is the command, not shell code and prints the output like this:
DIR: dir1
DIR: dir2
fileA
DIR: dir3
fileC
fileB
How to print the path from base directory for every dir or file from the base dir? Could someone help me to work it out?
Not clear what you want maybe,
find . -type d -printf 'Directory: %p\n' -o -type f -printf 'File: %p\n'
However to see the subtree of a directory, I find more useful
find "$dirname" -type f
To answer comment it can also be done in pure bash (builtin without external commands), using a recursive function.
rec_find() {
local f
for f in "$1"/*; do
[[ -d $f ]] && echo "Directory: $f" && rec_find "$f"
[[ -f $f ]] && echo "File: $f"
done
}
rec_find "$1"
You can use tree command. Key -L means max depth. Examples:
tree
.
├── 1
│   └── test
├── 2
│   └── test
└── 3
└── test
3 directories, 3 files
Or
tree -L 1
.
├── 1
├── 2
└── 3
3 directories, 0 files
Create your test.sh with the below codes. Here you are reading command line parameter in system variable $1 and provides parameter to find command.
#!/bin/bash #in which shell you want to execute this script
find $1 | sed -e "s/[^-][^\/]*\// |/g"
Now how will it work:-
./test.sh /home/usr/Dekstop/myDirectory #you execute this command
Here command line parameter will be assign into $1. More than one parameter you can use $1 till $9 and after that you have to use shift command. (You will get more detail information online).
So your command will be now:-
#!/bin/bash #in which shell you want to execute this script
find /home/usr/Dekstop/myDirectory | sed -e "s/[^-][^\/]*\// |/g"
Hope this will help you.

How to use 'mv' command to move files except those in a specific directory?

I am wondering - how can I move all the files in a directory except those files in a specific directory (as 'mv' does not have a '--exclude' option)?
Lets's assume the dir structure is like,
|parent
|--child1
|--child2
|--grandChild1
|--grandChild2
|--grandChild3
|--grandChild4
|--grandChild5
|--grandChild6
And we need to move files so that it would appear like,
|parent
|--child1
| |--grandChild1
| |--grandChild2
| |--grandChild3
| |--grandChild4
| |--grandChild5
| |--grandChild6
|--child2
In this case, you need to exclude two directories child1 and child2, and move rest of the directories in to child1 directory.
use,
mv !(child1|child2) child1
This will move all of rest of the directories into child1 directory.
Since find does have an exclude option, use find + xargs + mv:
find /source/directory -name ignore-directory-name -prune -print0 | xargs -0 mv --target-directory=/target/directory
Note that this is almost copied from the find man page (I think using mv --target-directory is better than cpio).
First get the names of files and folders and exclude whichever you want:
ls --ignore=file1 --ignore==folder1 --ignore==regular-expression1 ...
Then pass filtered names to mv as the first parameter and the second parameter will be the destination:
mv $(ls --ignore=file1 --ignore==folder1 --ignore==regular-expression1 ...) destination/
This isn't exactly what you asked for, but it might do the job:
mv the-folder-you-want-to-exclude somewhere-outside-of-the-main-tree
mv the-tree where-you-want-it
mv the-excluded-folder original-location
(Essentially, move the excluded folder out of the larger tree to be moved.)
So, if I have a/ and I want to exclude a/b/c/*:
mv a/b/c ../c
mv a final_destination
mkdir -p a/b
mv ../c a/b/c
Or something like that. Otherwise, you might be able to get find to help you.
This will move all files at or below the current directory not in the ./exclude/ directory to /wherever...
find -E . -not -type d -and -not -regex '\./exclude/.*' -exec echo mv {} /wherever \;
ls | grep -v exclude-dir | xargs -t -I '{}' mv {} exclude-dir
rename your directory to make it hidden so the wildcard does not see it:
mv specific_dir .specific_dir
mv * ../other_dir
#!/bin/bash
touch apple banana carrot dog cherry
mkdir fruit
F="apple banana carrot dog cherry"
mv ${F/dog/} fruit
# this removes 'dog' from the list F, so it remains in the
current directory and not moved to 'fruit'
Inspired by #user13747357 's answer.
First you can ls the file and filter them by:
ls | egrep -v '(dir_name|file_name.ext)'
Then you can run the following command to move the files except the specific ones:
mv $(ls | egrep -v '(dir_name|file_name.ext)') target_dir
* Note that I tested this inside a specific directory. Cross-directory operation should be more carefully executed :)
suppose you directory is
.
├── dir1
│ └── a.txt
├── dir2
│ ├── b.txt
│ └── hello.c
├── file1.txt
├── file2.txt
└── file3.txt
and you gonna put file1 file2 file3 into dir2.
you can use
mv $(ls -p | grep -v /) /dir2 to finish it, because
ls -p | grep -v / will print all files except directory in cwd.
For example, if I want to move all files/directories - except a specified file or directory - inside "var/www/html" to a sub-folder named "my_sub_domain", then I use "mv" with the command "!(what_to_exclude)":
$ cd /var/www/html
$ mv !(my_sub_domain) my_sub_domain
To exclude more I use "|" to seperate file/directory names:
$ mv !(my_sub_domain|test1.html) my_sub_domain
mv * exclude-dir
was the perfect solution for me

Resources