Shell Script Rename - linux

Now I have the rename script like this:
cd /Users/KanZ/Desktop/Project/Test/
n=1
for file in *.jpg; do
echo $file
prefix=M
file_name=M$n.jpg
echo $file_name
n=$(( $n+1 ))
mv $file $file_name
done
The first if I run script, the all of jpg files will be M1.jpg M2.jpg M3.jpg but if I add the new file name A1.jpg to this directory and run script again. All of M1.jpg M2.jpg M3.jpg will be replaced by M4.jpg(before run script, this file named A1.jpg) because the first letter is A. It come before M. This is my problem. I would like to get M1 M2 M3 M4.jpg and every time if there are new files coming, this script will generate the continue name like M5.jpg M6.jpg . How is the code look like? Hope you understand that. Thank u for helping

The other answers given so far don't show you any good practice.
Here's one possibility that assumes that the numbering is continuous (i.e., no jump from M4.jpg to M6.jpg). Without that assumption, the script is safe (it won't overwrite existing files, but will not rename certain files).
#!/bin/bash
shopt -s extglob
shopt -s nullglob
cd /Users/KanZ/Desktop/Project/Test/
# Determine how many files MX.jpg you have (with X a number):
files=( M+([[:digit:]]).jpg )
n=${#files[#]}
for file in !(M+([[:digit:]])).jpg; do
file_name=M$((++n)).jpg
mv -vn -- "$file" "$file_name"
done
The -n option to mv: no clobber (do not overwrite an already existing file)
The -v option to mv: be verbose (so you don't need the echos you had)
The -- thing: end of options, only arguments will be found behind (just in case some files will start with a hyphen, that would confuse mv).

This little script will do it.
cd /Users/KanZ/Desktop/Project/Test/
n=$(ls -1 | grep '^M' -c) # find the number for M files
ls -1 *.jpg| grep -v '^M' | while read file # for each of the non-M files
do
n=$(($n+1));
new_file=M$n.jpg # new name with next available M file name
mv "$file" "$new_file" # rename with new file name
done
See how it works.
test/rename$ for x in 1 2 3; do echo A$x> A$x.jpg; done;
test/rename$ ls
A1.jpg A2.jpg A3.jpg
test/rename$ cat ../rnm.sh
#!/bin/bash
cd "$1"
n=$(ls -1 | grep '^M' -c)
nfiles=$(ls -1 *.jpg| grep -v '^M');
for file in $nfiles;
do
n=$(($n+1));
new_file=M$n.jpg
mv $file $new_file
done
test/rename$ bash ../rnm.sh ./
test/rename$ ls
M1.jpg M2.jpg M3.jpg
test/rename$ for x in 1 2 ; do echo B$x> B$x.jpg; done;
test/rename$ ls
B1.jpg B2.jpg M1.jpg M2.jpg M3.jpg
test/rename$ bash ../rnm.sh ./
test/rename$ ls
M1.jpg M2.jpg M3.jpg M4.jpg M5.jpg
test/rename$ for x in *.jpg; do echo $x; cat $x; done;
M1.jpg
A1
M2.jpg
A2
M3.jpg
A3
M4.jpg
B1
M5.jpg
B2

The way i understood your requirement is :
Change all the .jpg file names from what ever name they have to M*.jpg with an incremental number following M.
During multiple runs of the program the count should increase from the highest value of M file that was setup during the last run. For example if the first run of the program created M1, M2, M3 and then you add more files they should start from M4, M5, M6.
But the M1, M2, M3 itself should not be changed.
So a couple of suggestions :
you are setting n=1 at the start of the script. You should use the highest value of Mx.jpg that exists in the file from the previous run.
Second, when you are looking for files and iterating them, exclude all M*.jpg files, so that their names are not replaced.
Do you think this is your requirement. If you have any additional inputs please add a comment. Will send out the script in the next comment.
Test Cases :
Existing Files, X.jpg, ASG.jpg, GER.jpg
File names after the run : M1.jpg, M2.jpg, M3.jpg
If you just run the script once more after the first run with no additional files: There should not be any change in the file names : M1.jpg, M2.jpg, M3.jpg.
Now Add more files : Z.jpg, A.jpg, B.jpg
The file names should be changed as follows :
M1.jpg => M1.jpg, M2.jpg => M2.jpg, M3.jpg => M3.jpg, A.jpg => M4.jpg, B.jpg => M5.jpg, Z.jpg => M6.jpg.
So the file names will be M1.jpg, M2.jpg, M3.jpg, M4.jpg, M5.jpg, M6jpg.

Related

loop through a directory and check if it exist in another directory

I have a folder with 20000 files in directory A and another folder
with 15000 file in another directory B i can loop through a directory
using:
DIR='/home/oracle/test/forms1/'
for FILE in "$DIR"*.mp
do
filedate=$( ls -l --time-style=+"date %d-%m-%Y_%H-%M" *.fmx |awk '{print $8 $7}')
echo "file New Name $FILE$filedate "
# echo "file New Name $FILE is copied "
done
I need to loop through all the files in directory A and check if they
exist in directory B
I tried the following but it doesn't seem to work:
testdir='/home/oracle/ideatest/test/'
livedir='/home/oracle/ideatest/live/'
for FILET in "$testdir" #
do
testfile=$(ls $FILET)
echo $testfile
for FILEL in "$livedir"
do
livefile=$(ls $FILEL)
if [ "$testfile" = "$livefile" ]
then
echo "$testfile"
echo "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
else
echo "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
fi
done
done
i'am trying to fix the result of years of bad version control we have
that very oly script that send a form to live enviorment but every
time it's compiled and sent the live version is named like
(testform.fmx) but in test dir there is like 10 files named like
(testform.fmx01-12-2018)
(testform.fmx12-12-2017)(testform.fmx04-05-2016) as a reuslt we lost
track of the last source sent to live enviroment that's why i created
this
filedate=$( ls -l --time-style=+"date %d-%m-%Y_%H-%M" *.fmx |awk
'{print $8 $7}')
echo "file New Name $FILE$filedate "
to match the format and loop through each dir and using ls i can find the last version by matching the size and the year and month
You can basicly use diff command to compare the files and directories. diff folderA folderB
I think you do not really need to use a loop for that..
If really you want to use a loop around, you may want to compare the files as well.
#!/bin/bash
DIR1="/home/A"
DIR2="/home/B"
CmpCmn=/usr/bin/cmp
DiffCmn=/usr/bin/diff
for file1 in $DIR1/*; do #Get the files under DIR1 one by one
filex=$(basename $file1) #Get only the name ofthe ile
echo "searching for $filex"
$DiffCmn $filex $DIR2 #Check whether the file is under DIR2 or not
if [ $? -ne 0 ]
then
echo " No file with $filex name under $DIR2 folder"
else
echo " $filex exists under $DIR2"
$CmpCmn $file1 $DIR2/$filex #Compare if the files are exactly same
if [ $? -ne 0 ]
then
echo " $filex is not same"
else
echo " $filex is the same"
fi
fi
done
This code is based on the code in the question:
testdir='/home/oracle/ideatest/test/'
livedir='/home/oracle/ideatest/live/'
shopt -s nullglob # Globs that match nothing expand to nothing
shopt -s dotglob # Globs match files whose names start with '.'
for testpath in "$testdir"*
do
[[ -f $testpath ]] || continue # Skip non-files
testfile=${testpath##*/} # Get file (base) name
printf '%s\n' "$testfile"
livepath=${livedir}${testfile} # Make path to (possible) file in livedir
if [[ -f $livepath ]]
then
printf '%s\n' "$testfile"
echo "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
else
echo "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
fi
done
You need to find files that are common in both A and B directories.
comm -12 \
<(cd A && find . -type f -maxdepth 1 | sort) \
<(cd B && find . -type f -maxdepth 1 | sort)
Live version available at tutorialspoint.
How it works? find's list the files in both A and B directories and comm displays only files/lines common in both inputs. comm needs input to be sorted, that's why | sort
Don't parse ls output. ls is for nice, formatted output. Use find . and parse it's output.

Bash script to delete backup files within a given directory & calling a calling that script in another method?

I have a test harness called "test.sh" that will create 24 backup files in a directory called "D1" with extensions .txt~, .bak, and 12 files that start with #. In this test harness I have to call another method called "clean.sh" that will delete all the files in the given directory (in this case D1).
This is my test.sh:
#!/bin/bash
#Create new directory called D1
mkdir D1
#cd into D1
cd ./D1
#Create 12 backup files .bak in D1 (using for loop)
for i in `seq 1 12`;
do
#echo $I
touch file$i.bak D1
done
#Create 12 backup files .txt~ in D1 (using set)
touch {a..l}.txt~ D1
#Create 12 files prefix # in D1 (using while loop)
COUNTER=0
while [ $COUNTER -lt 12 ]; do
touch \#$COUNTER.txt D1
let COUNTER=COUNTER+1
done
#Once finished creating backup files in all 3 categories, do an ls -l
ls -l
#Invoke clean method here
./cleanUp.sh
#Do final ls - l
ls - l
This is clean.sh:
#!/bin/bash
#Need to pass a directory in as argument
for i in "$#"
do
echo file is: $I
done
I'm not sure how to pass in a directory as an argument and calling a method in another method is confusing. Thanks!
Your script is a nice start.
The touch commands can do without the parameter D1, your cleanup.sh call needs a parameter. The parameter ${PWD} is given by the shell, thats easy in this case.
(you can also give a parameter like /home/mina/test2/D1).
The script will look like
!/bin/bash
#Create new directory called D1
mkdir D1
#cd into D1
cd ./D1
#Create 12 backup files .bak in D1 (using for loop)
for i in `seq 1 12`;
do
#echo $I
touch file$i.bak
done
#Create 12 backup files .txt~ in D1 (using set)
touch {a..l}.txt~
#Create 12 files prefix # in D1 (using while loop)
COUNTER=0
while [ $COUNTER -lt 12 ]; do
touch \#$COUNTER.txt
let COUNTER=COUNTER+1
done
#Once finished creating backup files in all 3 categories, do an ls -l
ls -l
#Invoke clean method here
./cleanUp.sh ${PWD}
#Do final ls - l
ls - l
Now the cleanup script.
First change $I into $i, the variable name is case sensitive.
With for i in "$#" you loop through the parameters (only the name of the dir),
and you would like to go through the diffferent filenames.
Try the following alternatives:
# Let Unix expand the * variable
for i in $1/*; do
# Use a subshell
for i in $(ls "$#"); do
# use a while-loop
ls $# | while read i; do
# use find and while
find "$1" -type f | while read i; do
# use find and -exec
find "$1" -type f -exec echo File is {} \;
# use find and xargs
find "$1" -type f | xargs echo File is

How to rename all files in a directory adding a prefix

I first create 10 digit random number
export mask=$RANDOM$RANDOM$RANDOM; let "mask %= 10000000000";
This command works well
for i in /home/testing/*; do mv "$i" "$mask$i"; done
The problem with the above command is that it only works when I am in /home/testing. As soon as I move the script, i get this error
mv: cannot move ‘/home/testing/rec001.mp4’ to ‘3960731225/home/testing/rec001.mp4’: No such file or directory
What am I doing wrong here?
You need to separate the path from the filename before you apply the mask. For example, to use in a script where the directory is passed as an argument to the script:
path="$1"
## Note: this assumes you are exporting mask earlier. If not, set mask here
for i in "${path}"/*; do
dir="${i%/*}" # path component
ffname="${i##*/}" # filename component
mv "$i" "${dir}/${mask}${ffname}"
done
This will apply mask to all files in the given directory, no matter where the directory is.
An example of a script that incorporates this is shown below. You can save this script wherever you like. You can either make it executable chmod 0755 scriptname or call it with bash scriptname. To use the script, add the path you want to prefix the files in as the first argument. E.g bash scriptname /path/to/files (or just scriptname /path/to/files if you made it executable):
#!/bin/bash
# validate input
[ -n "$1" ] || {
printf "error: insufficient input. Usage: %s /path/to/files\n" "${0//\//}"
exit 1
}
# validate directory
[ -d "$1" ] || {
printf "error: directory not found: '%s'\n" "$1"
exit 1
}
path="$1"
## Note: this assumes you are exporting mask earlier. If not, set mask here
## validate mask set and is 10 chars (added per comment)
[ -n "$mask" -a "${#mask}" -eq 10 ] || {
printf "error: mask '%s' either unset or not 10 characters\n" "$mask"
exit 1
}
# move files
for i in "${path}"/*; do
[ -f "$i" ] || continue # if not file, skip
dir="${i%/*}" # path component
ffname="${i##*/}" # full filename component (with .ext)
mv "$i" "${dir}/${mask}${ffname}"
done
Here is a sample of what moves would take place with the script named prefix.sh when called on the directory dat in the current working directory and when called on ~/tmp outside the current directory:
output (mask=3960731225):
$ ./prefix.sh dat
dat/f1f2.dat => dat/3960731225f1f2.dat
dat/field.txt => dat/3960731225field.txt
dat/flop.txt => dat/3960731225flop.txt
dat/hh.dat => dat/3960731225hh.dat
dat/hh1.dat => dat/3960731225hh1.dat
dat/hostnm => dat/3960731225hostnm
dat/hosts.dat => dat/3960731225hosts.dat
$ ./prefix.sh ~/tmp
/home/david/tmp/tcpd.tar.xz => /home/david/tmp/3960731225tcpd.tar.xz
/home/david/tmp/tcpdump-capt => /home/david/tmp/3960731225tcpdump-capt
/home/david/tmp/tcpdump.new.1000 => /home/david/tmp/3960731225tcpdump.new.1000
/home/david/tmp/test => /home/david/tmp/3960731225test
There is two commands that is quite helpful, basename and dirname.
They will give you the dir part and the filename, have a look at this test script.
#!/bin/bash
mask=$RANDOM$RANDOM$RANDOM; let "mask %= 10000000000";
echo $mask
mkdir -p testing
> testing/nisse.txt
> testing/guste.txt
> testing/berra.txt
ls testing/
for i in testing/*
do
file=$(basename $i)
dir=$(dirname $i)
newfile=$mask$file
echo $i $dir $file $newfile
mv "$dir/$file" "$dir/$newfile"
done
ls testing/
And it will output:
247639260
berra.txt gusten.txt nisse.txt
testing/berra.txt testing berra.txt 247639260berra.txt
testing/guste.txt testing guste.txt 247639260guste.txt
testing/nisse.txt testing nisse.txt 247639260nisse.txt
247639260berra.txt 247639260guste.txt 247639260nisse.txt
Please note that I wrote it very verbose to make it more clear and readable.
How about adding a cd command before your command now, wherever you want to move the script it works,
cd /home/testing
for i in /home/testing/*; do mv "$i" "$mask$i"; done

Export variables to another script

I am making 2 scripts. The first script is going to take a file, and then move it to a directory named "Trash". The second script will recover this file and send it back to it's original directory. So far I have the first script moving the file correctly.
Here is my code so far:
For delete.sh
FILE=$1
DATEDELETED=$(date)
DIRECTORY=$(pwd)
mv $1 Trash
echo $FILE
echo $DATEDELETED
echo $DIRECTORY
Output:
trashfile
Sun Mar 2 21:37:21 CST 2014
/home/user
For undelete.sh:
PATH=/home/user/Trash
for file in $PATH
do
$file | echo "deleted on" | date -r $file
done
echo "Enter the filename to undelete from the above list:"
EDIT: So I realized that I don't need variables, I can just list all the files in the Trash directory and edit the output to what I want. I'm having a little trouble with my for statement though, I'm getting these two errors: ./undelete.sh: line 6: date: command not found
./undelete.sh: line 6: /home/user/Trash: Is a directory. So I'm not exactly sure what I'm doing wrong in my for statement.
Here is the expected output:
file1 deleted on Tue Mar 16 17:15:34 CDT 2010
file2 deleted on Tue Mar 16 17:15:47 CDT 2010
Enter the filename to undelete from the above list:
Well I've accomplished what could be a workaround for what your scenario is trying to accomplish.
Basically you can enter echo "script2variable=$script1variable" >> script2.sh from script1.sh. Then use the source command to call that script later from any script you desire. Might have to just play with the theories involved.
Good Luck!
Delete Script file
#!/bin/bash
# delete.sh file
# Usage: ./delete.sh [filename]
#DATEDELETED=$(date) #not best solution for this kind of application
DIR=$(pwd)
fn=$1
#Specify your trash directory or partition
trash=~/trash
#Set path and filename for simple use in the script
trashed=$trash/$fn.tgz
#Send variables to new manifest script.
echo "#!/bin/bash" > $1.mf.sh
echo "DIR=$DIR" >> $1.mf.sh
# Questionable need?
#echo "DATEDELETED=$DATEDELETED" >> $1.mf.sh
echo "mv $1 $DIR" >> $1.mf.sh
echo Compressing
tar -cvpzf $trashed $1 $1.mf.sh
if [ $? -ne 0 ]; then
echo Compression Failed
else
echo completed trash compression successfully
echo Trashbin lists the file as $trashed
rm $1 -f
rm $1.mf.sh -f
echo file removed successfully
fi
exit 0
Restore Script File
#!/bin/bash
# restore.sh
# Usage: ./restore.sh
# filename not required for this script, use index selection
fn=$1
#SET LOCATION OF TRASH DIRECTORY
trash=~/trash
listfile=($(ls $trash))
#SET COUNTER FOR LISTING FILES = 0
i=0
#THIS IS JUST A HEADER FOR YOUR OUTPUT.
clear #cleanup your shell
echo -e INDEX '\t' Filename '\t' '\t' '\t' Date Removed
#Echo a list of files from the array with the counter.
for FILES in "${listfile[#]}"
do
echo -e $i '\t' $FILES '\t' "deleted on $(date -r $trash/$FILES)"
let "i += 1"
done
#Output total number of items from the ls directory.
echo -e '\n' $i file\(s\) found in the trash!
# 1 Offset for 1 = 0, 2 = 1, and so on.
let "i -= 1"
#Require input of a single, double, or triple digit number only.
#Loop back prompt if not a number.
while true;
do
read -p "Enter an index number for file restoration: " indx
case $indx in
[0-9]|[0-9][0-9]|[0-9][0-9][0-9] ) break ;;
* ) read -p "Please enter a valid number 0-$i: " indx ;;
esac
done
#
script=$(echo ${listfile[$indx]}|sed 's/\.tgz/\.mf.sh/g')
tar -xvpzf $trash/${listfile[$indx]}
rm $trash/${listfile[$indx]}
sleep 2
chmod +x $script
source $script
rm $script
Run the script with source
source <yourscript>
or
. ./<yourscript>
In your case
. ./delete.sh && ./undelete.sh
Hope this will help

How to make a bash script classify files in folders by a specific number

I found a script, here on StackOverflow, which I modified a little.
The script classifies all files from a folder in subfolders, each subfolder having only 8 files. But I have files with such names 0541_2pcs.jpg. 2pcs means two pieces (copies).
so I would like the script to take this into count when dividing files to each folder. e.g. a folder may have 6 files and this 0541_2pcs.jpg which literally means 2 files and so on, depending on the number indicated in the file's name.
This is the script:
cd photos;
dir="${1-.}"
x="${1-8}"
let n=0
let sub=0
while IFS= read -r file ; do
if [ $(bc <<< "$n % $x") -eq 0 ] ; then
let sub+=1
mkdir -p "Page-$sub"
n=0
fi
mv "$file" "Page-$sub"
let n+=1
done < <(find "$dir" -maxdepth 1 -type f)
Can anyone help me?
You can add a test for whether a file name contains the string "2pcs" via code like [ ${file/2pcs/x} != $file ]. In the script shown below, if that test succeeds then n is incremented a second time. Note, if the eighth file tested for inclusion in a directory is a multi-piece file, you will end up with one too many files in that directory. That could be handled by additional testing the script doesn't do. Note, there's no good reason for your script to call bc to do a modulus, and setting both of dir and x from the first parameter doesn't work; my script uses two parameters.
#!/bin/bash
# To test pagescript, create dirs four and five in a tmp dir.
# In four, say
# for i in {01..30}; do touch $i.jpg; done
# for i in 03 04 05 11 16 17 18; do mv $i.jpg ${i}_2pcs.jpg; done
# Then in the tmp dir, say
# rm -rf Page-*; cp four/* five/; ../pagescript five; ls -R
#cd photos; # Leave commented out when testing script as above
dir="${1-.}" # Optional first param is source directory
x=${2-8} # Optional 2nd param is files-per-result-dir
let n=x
let sub=0
for file in $(find "$dir" -maxdepth 1 -type f)
do # Uncomment next line to see values as script runs
#echo file is $file, n is $n, sub is $sub, dir is $dir, x is $x
if [ $n -ge $x ]; then
let sub+=1
mkdir -p Page-$sub
n=0
fi
mv "$file" Page-$sub
[ ${file/2pcs/x} != $file ] && let n+=1
let n+=1
done

Resources