Sort text files in bash - shell

I have a number of text files in the current directory. Each file contains data about a movie.The content of each file will be according to the following format:
Movie name
Storyline
Director name
Year of release
How can I organize the files according to the name of the directors by a shell script. Movies made by same director will get moved to a folder named after the director. How can I do this by writing shell script?

The rough solution will be
for file in *; do
director=$( some code here depending on requirements clarification )
mkdir -p "$director" # quotes are crucial!
# -p suppresses errors if dir already exists.
mv -v "$file" "$director"
done

Assuming that the filename of the movie is always the first line, and the Director name is always the next to last. You could do it something similar to this:
#!/bin/bash
MOVIES_DIR=/path/to/your/movies
for FILE in ${MOVIES_DIR}/*.txt; do
# Movie name will always be the first line
MOVIE=$(sed '1q;d' "$FILE")
# Director will always be the next to last line
DIRECTOR=$(tac "$FILE" | sed '2q;d')
# Make the director folder
mkdir -p "${MOVIES_DIR}/${DIRECTOR}"
# Find inside the `MOVIES_DIR` files with the movie name with any extension
# That is NOT .txt and moves them to the proper director folder.
find "${MOVIES_DIR}" -maxdepth 1 -name "${MOVIE}.*" -not -name '*.txt' -print0 |
while IFS= read -r -d '' NAME; do
FILENAME=$(printf '%s\n' "$NAME")
mv "$FILENAME" "${MOVIES_DIR}/${DIRECTOR}"
done
done
Edit:
It was my misunderstanding that you wanted to move movies with the same name in the text file into the appropriate director folder. I now see you are intending to move the text files themselves. I downloaded all the text files from the link you provided, and it successfully sorted all of them into the appropriate folder:
#!/bin/bash
MOVIES_DIR=/path/to/your/files
for FILE in ${MOVIES_DIR}/*.txt; do
# I thought the description was on one line, but it looks like it can span
# multiple. So get the next to last line instead, since year is only one line. I also changed this in the first script.
DIRECTOR=$(tac "$FILE" | sed '2q;d')
DIRECTOR_PATH="$MOVIES_DIR/$DIRECTOR"
mkdir -p "$DIRECTOR_PATH"
# Move the current text file to the Director folder.
mv "$FILE" "$DIRECTOR_PATH"
done
The first script should sort all movies into the director folders, assuming that the text files are in the same directory as the movies.
The second script should sort all text files into the appropriate director folders.

Related

How do I extract a file name into 2 parts, making one into directory and the other one inside of it?

I'm trying to sort all mp3 files by artist and name. At the moment, they're in 1 giant file name.
E.g Artist - Song name.mp3
I want to convert this to
Artist/Song name.mp3
Here's what I've tried so far. In this case, I was using a test file named "hi\ -\ hey":
#!/bin/bash
# filters by all .mp3 extensions in the current working directory
for f in *.mp3; do
# extract artist and song name and remove spaces
artist=${f% -*}
song=${f#*- }
#make directory with the extracted artist name and move + rename the file into the directory
mkdir -p $artist
mv $f $artist/$song;
done
For some reason, it's creating a directory with the song name instead of the artist in addition to a load of errors:
mv: cannot move 'hey.mp3' to a subdirectory of itself, 'hey.mp3/hey.mp3'
mv: cannot stat 'hi': No such file or directory
mv: cannot stat '-': No such file or directory
mv: 'hey.mp3/hi' and 'hey.mp3/hi' are the same file
mv: cannot stat '-': No such file or directory
By far the simplest way of doing this is to use rename a.k.a. Perl rename.
Basically, you want to replace the sequence SPACE-DASH-SPACE with a forward slash directory separator, so the command is:
rename --dry-run -p 's| - |/|' *mp3
Sample Output
'Artist - Song name.mp3' would be renamed to 'Artist/Song name.mp3'
'Artist B - Song name 2.mp3' would be renamed to 'Artist B/Song name 2.mp3'
If that looks correct, just remove --dry-run and run it again for real. The benefits of using rename are:
it can do a dry-run to test before you run for real
it will create all necessary directories with the -p option
it will not clobber (overwrite) files without warning
you have the full power of Perl available to you and can make your renaming as sophisticated as you wish.
Note that you can install on macOS with homebrew:
brew install rename
Just in case you don't have the rename utility. A fix on your original script.
for f in *.mp3; do
# extract artist and song name and remove spaces
artist=${f%% -*}
song=${f##*- }
#make directory with the extracted artist name and move + rename the file into the directory
echo mkdir -p -- "$artist" && echo mv -- "$f" "$artist/$song"
done
Remove the echo If you're satisfied with the output.
Assuming there are many files, it's probably much faster to do this using pipes instead of a for loop. This has the additional advantage of avoiding complicated bash-specific syntax and using core unix/linux command line programs instead.
find *-*.mp3 |
sed 's,\([^-]\+\)\s*-\s*\(.*\),mkdir -p "\1"; mv "&" "\1"/"\2",' |
bash
Explanation:
This find to find all the files matching -.mp3 in the current directory.
This sed command changes each line to a command string, e.g.:
aaa - bbb.mp3
->
mkdir -p "aaa"; mv "aaa - bbb.mp3" "aaa"/"bbb.mp3"
The bash command runs each of those command strings.
you can try this.
#!/usr/local/bin/bash
for f in *.mp3
do
artist=`echo $f | awk '{print $1}' FS=-`
song=`echo $f | awk '{print $2}' FS=-`
mkdir -p $artist
mv $artist-$song $song
mv $song ./$artist
done
here I am using two variable artist and song. as your test file name is "hi\ -\ hey" so I change the awk delimiter to "-" to store variable according to it.
we don't need to use awk..by using bash parameter expansion.... it is working.
#!/usr/local/bin/bash
for f in *.mp3
do
artist=`echo ${f%-*}`
song=`echo ${f#*-}`
mkdir -p $artist
mv $artist-$song $song
mv $song ./$artist
done

Linux bash script to copy files by list

I'm new in bash and I need a help please. I have a file call list.txt containing pattern like
1210
1415
1817
What I want to do is to write a bash script which will copy all file in my current directory which name contain that pattern towards a new directory called toto.
Example of my file in the current directory :
1210_ammm.txt
1415_xdffmslk.txt
1817_lsmqlkksk.txt
201247_kksjdjdjd.txt
The goal is to copy 1210_ammm.txt, 1415_xdffmslk.txt, 1817_lsmqlkksk.txt to toto.
Transferred from an 'answer'.
My list.txt and toto directory are in my current directory. That is what I try
#!/bin/bash
while read p; do # read my list file
for i in `find -name $p -type f` # find all file match the pattern
do
cp $i toto # copy all files find into toto
done
done < partB.txt
I don't have an error but it doesn't do the job.
Here is what you need to implement :
read tokens from an input file
for each token
search the files whose name contain said token
for each file found
copy it to toto
To read tokens from the input file, you can use a read command in a while loop (and the Bash FAQ generally, and Bash FAQ 24 specifically.
To search files whose name contain a string, you can use a for loop and globbing. For example, for file in ./*test*; do echo $file; done will print the name of the files in the current directory which contain test.
To copy a file, use cp.
You can check this ideone sample for a working implementation.
Use below script:
cp "$(ls | grep -f list.txt)" toto
ls | grep -f list.txt will grep for the pattern found in list.txt in the ls output.
cp copies the matched files to toto directory.
NOTE: If list.txt and toto are not in current directory, provide absolute paths in the script.
I needed this too, I tried #Zaziln's answer, but it gave me errors. I just found a better answer. I think others will be interested too.
mapfile -t files < test1.txt
cp -- "${files[#]}" Folder/
I found it on this post --> https://unix.stackexchange.com/questions/106219/copy-files-from-a-list-to-a-folder#106231

Move files to the correct folder in Bash

I have a few files with the format ReportsBackup-20140309-04-00 and I would like to send the files with same pattern to the files as the example to the 201403 file.
I can already create the files based on the filename; I would just like to move the files based on the name to their correct folder.
I use this to create the directories
old="directory where are the files" &&
year_month=`ls ${old} | cut -c 15-20`&&
for i in ${year_month}; do
if [ ! -d ${old}/$i ]
then
mkdir ${old}/$i
fi
done
you can use find
find /path/to/files -name "*201403*" -exec mv {} /path/to/destination/ \;
Here’s how I’d do it. It’s a little verbose, but hopefully it’s clear what the program is doing:
#!/bin/bash
SRCDIR=~/tmp
DSTDIR=~/backups
for bkfile in $SRCDIR/ReportsBackup*; do
# Get just the filename, and read the year/month variable
filename=$(basename $bkfile)
yearmonth=${filename:14:6}
# Create the folder for storing this year/month combination. The '-p' flag
# means that:
# 1) We create $DSTDIR if it doesn't already exist (this flag actually
# creates all intermediate directories).
# 2) If the folder already exists, continue silently.
mkdir -p $DSTDIR/$yearmonth
# Then we move the report backup to the directory. The '.' at the end of the
# mv command means that we keep the original filename
mv $bkfile $DSTDIR/$yearmonth/.
done
A few changes I’ve made to your original script:
I’m not trying to parse the output of ls. This is generally not a good idea. Parsing ls will make it difficult to get the individual files, which you need for copying them to their new directory.
I’ve simplified your if ... mkdir line: the -p flag is useful for “create this folder if it doesn’t exist, or carry on”.
I’ve slightly changed the slicing command which gets the year/month string from the filename.

shell script to rename folder by info from file inside

well, I got a set of folders each containing a *.nfo file from XBMC (beside other files such as videos and pictures etc).
I want to rename the folder by strings inside the *.nfo file.
The relevant content of such a *.nfo file:
...
<title>Filmtitle</title>
...
<year>2011</year>
...
<director>Werner Herzog</director>
...
UPDATE: here is an unmodified, original .nfo file from XBMC
movie.nfo
I tried a lot with find exec and grep, but I did not really get anythin usable...
In the example above the folder should have the name "Filmtitel [2011, Werner Herzog]"
Maybe someone can help me out!
Try the following script. It extracts the title, year and director from each file and then renames the directory:
find . -type f -name "*.nfo" -print0 | while IFS= read -r -d $'\0' file
do
title="$(sed 's|.*<title>\(.*\)</title>.*|\1|g' $file)"
year="$(sed 's|.*<year>\(.*\)</year>.*|\1|g' $file)"
director="$(sed 's|.*<director>\(.*\)</director>.*|\1|g' $file)"
dirName="${file%/*}"
newDirName="${dirName%/*}/$title [$year, $director]"
# mv "$dirName" "$newDirName"
echo mv "$dirName" "$newDirName"
done
(Simply uncomment the mv command if you are happy that the commands being printed out are correct.)

Rename files within folders to folder names while retaining extensions

I have a large repository of media files that follow torrent naming conventions- something unpleasant to read. At one point, I had properly named the folders that contain said files, but not want to dump all the .avi, .mkv, etc files into my main media directory using a bash script.
Overview:
Current directory tree:
Proper Movie Title/
->Proper.Movie.Title.2013.avi
->Proper.Movie.Title.2013.srt
Title 2/
->Title2[proper].mkv
Movie- Epilogue/
->MOVIE EPILOGUE .AVI
Media Movie/
->MEDIAMOVIE.CD1.mkv
->MEDIAMOVIE.CD2.mkv
.
.
.
Desired directory tree:
Proper Movie Title/
->Proper Movie Title.avi
->Proper Movie Title.srt
Title 2.mkv
Movie- Epilogue.avi
Media Movie/
->Media Movie.cd1.mkv
->Media Movie.cd2.mkv
Though this would be an ideal, my main wish is for the directories with only a single movie file within to have that file be renamed and moved into the parent directory.
My current approach is to use a double for loop in a .sh file, but I'm currently having a hard time keeping new bash knowledge in my head.
Help would be appreciated.
My current code (Just to get access to the internal movie files):
#!/bin/bash
FILES=./*
for f in $FILES
do
if [[ -d $f ]]; then
INFILES=$f/*
for file in $INFILES
do
echo "Processing >$file< folder..."
done
#cat $f
fi
done
Here's something simple:
find * -type f -maxdepth 1 | while read file
do
dirname="$(dirname "$file")"
new_name="${dirname##*/}"
file_ext=${file##*.}
if [ -n "$file_ext" -a -n "$dirname" -a -n "$new_name" ]
then
echo "mv '$file' '$dirname/$new_name.$file_ext'"
fi
done
The find * says to run find on all items in the current directory. The -type f says you only are interested in files, and -maxdepth 1 limits the depth of the search to the immediate directory.
The ${file##*.} is using a pattern match. The ## says the largest left hand match to *. which is basically pulling everything off to the file extension.
The file_dir="$(dirname "$file")" gets the directory name.
Note quotes everywhere! You have to be careful about white spaces.
By the way, I echo instead of doing the actual move. I can pipe the output to a file, examine that file and make sure everything looks okay, then run that file as a shell script.

Resources