I've made this script, but I have difficulties to write the code for this situation:
in the trashcan there should be no file with the same name; in which case they should be renamed.
How can I solve?
Here is my code:
#!/bin/bash
help() {
echo "Options:"
echo "\"safe-rm pathname\" to delete, where the pathname can be absolute or relative"
echo "\"safe-rm --recover original pathname\" (including the /) to recover and restore a file or a directory in the original position"
echo "\"safe-rm --list\" to lists the trashcan's content"
echo "\"safe-rm --search\" to search a file in the trashcan"
echo "\"safe-rm --delete-older-than\" to delete files older than certain days"
}
delete() {
if [ ${PARAM1:0:1} = "/" ]; then
echo "You have insert an absolute pathname"
mkdir -p $(dirname $TRASH$PARAM1)
mv $PARAM1 $TRASH$PARAM1
else
echo "You have insert a relative pathname"
mkdir -p $(dirname $TRASH$(pwd)/$PARAM1)
mv $PARAM1 $TRASH$(pwd)/$PARAM1
fi
}
readonly TRASH=$HOME/.Trash;
readonly PARAM1=$1;
readonly PARAM2=$2;
mkdir -p $TRASH;
case "$PARAM1" in
"")
help
;;
--list)
echo "Trashcan's content"
cd $TRASH
find *
;;
--delete-older-than)
echo "Delete the files older than $PARAM2 days"
find $TRASH -mtime +$PARAM2 | xargs rm -rf
;;
--search)
echo "Search $PARAM2 among the trashcan's files"
cd $TRASH
find -name *$PARAM2*
;;
--recover)
echo "Recover the file/directory in the original position"
mkdir -p $(dirname $PARAM2)
mv $TRASH$PARAM2 $PARAM2
;;
*)
echo "Security delete a file/directory"
delete
;;
esac
exit 0
Quick and dirty solution:
if [[ -f $TRASH$PARAM ]]; then
mv "$PARAM1" "$TRASH$PARAM$RANDOM$RANDOM" # file exists
else
mv "$PARAM1" "$TRASH$PARAM" # ok, it is fine, file does not exist
fi
Also please note that you have to quote every variable in your script when it is passed as a parameter.
if [ ${PARAM1:0:1} = "/" ]; then must be changed to if [ "${PARAM1:0:1}" = "/" ]; then or even better if [[ ${PARAM1:0:1} = "/" ]]; then
mkdir -p $(dirname $TRASH$PARAM1) to mkdir -p "$(dirname "$TRASH$PARAM1")"
And so on...
Consider generating a trailing sum like sha1sum on your files when they are stored on the the trash can to prevent having conflicts with similar files. e.g.
$HOME/.Trash/home/user/same/path/same_name.9ce1f394b955306f7c450cbf0d96d2f17f6a1394
$HOME/.Trash/home/user/same/path/same_name.b0dc31b1919c02932892b59d0c0e365cd75629c6
When restoring those files you just have removed the sum like
/home/user/same/path/same_name
The solution could also prevent duplicates of truly the same files for the probable uniqueness of what sums could do.
If you trust sums enough you could also opt to not store the directories in the trash. Just their signatures with an extra info file on it like:
$HOME/.Trash/same_name.9ce1f394b955306f7c450cbf0d96d2f17f6a1394
$HOME/.Trash/same_name.9ce1f394b955306f7c450cbf0d96d2f17f6a1394.info
Where info contains the absolute path of the directory where the file is located.
/home/user/same/path/
You could even add other attributes on it like directory permissions, etc.
[File]
/home/user/same/path/same_name
[Attributes]
/home[ TAB ]USER:GROUP[ TAB ]0755
/home/user[ TAB ]USER:GROUP[ TAB ]0755
/home/user/same[ TAB ]USER:GROUP[ TAB ]0755
/home/user/same/path[ TAB ]USER:GROUP[ TAB ]0755
/home/user/same/path/same_name[ TAB ]USER:GROUP[ TAB ]0644
Basically sums are just concepts but you could add more tricks on your own base on it to make file existence in trash a little more certain to be unique, only that you have to consider that it could no longer prevent files with that are really the same to exist as two entries which could have been not necessary.
Also of course on your script if you want to support filenames with spaces and likes, always place your variables inside double-quotes to prevent those from word splitting which means they would could later be interpreted as two or more arguments to the command causing syntax error to the command or unexpected results in which some may be irrevocable.
do something "$var" "${etc}xyz"
Actually, I wrote that back in 2010. Now there is a trash-cli package for Ubuntu.
http://wiki.linuxquestions.org/wiki/Scripting#Command_Line_Trash_Can
Related
New to shell scripting and I want to test to see if the variables I created are valid directories and if not send the user into a while loop to enter the directory and only allow exit when a valid directory is entered.
So far this is what my script looks like:
~/bin/bash
source_dir="$1"
dest_dir="$2"
mkdir /#HOME/$source_dir
mkdir /#HOME$dest_dir
if [ -d "$source_dir" ]
then
echo "$source_dir is a valid directory"
fi
while [[ ! -d "$source_dir" ]]
do
echo "Please enter a valid directory"
read source_dir
done
Is there any way to combine these into a single statement?
The while code will never execute if the directory is valid. Therefore just move the echo "$source_dir is a valid directory" after the loop:
#!/bin/bash
source_dir="$1"
dest_dir="$2"
mkdir "$HOME/$source_dir"
mkdir "$HOME/$dest_dir"
until [[ -d "$source_dir" ]]
do
read -p "Please enter a valid directory" source_dir
done
echo "$source_dir is a valid directory"
Notes:
a few code typos were fixed, e.g. /#HOME$dest_dir should be "$HOME/$dest_dir".
any while ! can be shortened to until.
The above code lacks a few things:
It tries create a new dir, then if that fails, has the user enter an already existing directory. It might be better to let the user create a new directory, but only if it doesn't already exist.
It would be better to check if $dest_dir exists.
Here's a more thorough approach using a shell function:
#!/bin/bash
untilmkdir ()
{
d="$1";
until mkdir "$d" ; do
read -p "Please enter a valid directory: " d
[ -d "$d" ] && break
done;
echo "$d is a valid directory" 1>&2
echo "$d"
}
source_dir=$(untilmkdir "$HOME/$1")
dest_dir=$(untilmkdir "$HOME/$2")
Notes:
The prompts in untilmkdir are printed to stderr.
The name of whatever directory untilmkdir creates is printed to stdout.
Having untilmkdir print to both stderr and stdout allows storing the successfully created name to a variable.
I have this snippet:
#!/bin/bash
parent=/parent
newfolder=/newfolder
mkdir "$newfolder"
for folder in "$parent"/*; do
if [[ -d $folder ]]; then
foldername="${folder##*/}"
for file in "$parent"/"$foldername"/*; do
filename="${file##*/}"
newfilename="$foldername"_"$filename"
cp "$file" "$newfolder"/"$newfilename"
done
fi
done
I do need to turn it around in a way that the copied files would be named after the folder they are being moved to (e.g. moving to the /root/Case22 files would be renamed to case22_1.jpg, case22_2.docx, case22_3.JPG etc). The files would be copied from USB and both destination and source directries would be entered by the user. I have written everything else and it works apart from actual renaming and thought I could adapt this snippet.
thanks
p.s. the snippet is written by Jahid and found on stackoverflow
you can try something like this;
#!/bin/bash
parent=/root
a=1
for file in $parent/Case22*; do
filename="${file%.*}"
extension="${file##*.}"
newfilename=$(printf "$filename"_"$a"."$extension")
mv -- "$file" "$newfilename"
let a=a+1
done
Thanks for the help. I have found the solution and thought I might post it here in case someone else will be looking at this.
As the title suggests I needed a Linux shell script to copy and rename multiple files keeping original directory tree (the file source and archive locations would be specified by the user of the script). Here is the code that I came up with after few days research of different sources (it includes a trap so only one instance of script would be running at a time):
lockdir=/var/tmp/mylock #directory for lock file creation
pidfile=/var/tmp/mylock/pid #directory to get the process ID number
if ( mkdir ${lockdir} ) 2> /dev/null; then #main argument to create lock file
echo $$ > $pidfile #if successful script will proceed, otherwise it will skip to the else part of the statement at the end of the script
trap 'rm -rf "$lockdir"; exit $?' INT TERM EXIT #trap to capture reasons of script termination and removal of the lock file so it could be launched again
#start of the main script body, part of successful "if" statement
# read entry_for_testing_only #request for user entry to keep script running and try to run another script instance
findir="$2/$(basename "$1")" #variable that defines final directory to which files from USB will be copied
if [ ! -d "$1" ]; then #testing if first directory entry is a valid directory’’
echo "$1" "is not a directory"
echo ""
exit
else
if [ ! -d "$2" ]; then #testing if second entry is a valid directory
echo "archive directory non existant"
exit
else
if [ -d "$findir" ] && [ "$(ls -A "$findir")" ]; then #testing if second entry directory contains the same name folders and if the folders are empty - to avoid file overwriting
echo "such folder already there and it's not empty"
exit
else
if [ ! -d "$findir" ] ; then #last archive directory argument to create final archive directory
mkdir "$findir"
else true
fi
fi
fi
fi
rsync -a "$1"/ "$findir" #command to copy all files from the source to the archive retaining the directory tree
moved_files="$(find "$findir" -type f)" #variable that finds all files that have been copied to the archive directory
for file in $moved_files; do #start of the loop that renames copied files
counter="$((counter+1))" #incrementation variable
source_name="$(basename "$1")" #variable that captures the name of the source directory
new_name="$source_name"_"$counter" #variable that puts start of the file name and incrementation element together
if echo "$file" | grep "\." #argument that captures the extension of the file
then
extension="$(echo "$file" | cut -f2 -d. )"
else
extension=
fi
full_name="$new_name"."$extension" #variable that defines the final new name of the file
dir="$(dirname "${file}")" #variable that captures the directorry address of currently looped file
mv "$file" "$dir/$full_name" #move command to rename currently looped file with the final new name
done
#end of the main script body, unsuccessful "if" statement continues here
else
echo "Another instance of this script is already running. PID: $(cat $pidfile)"
fi
I tried incorrectly to add my question on to a very similar thread w/ good solutions here:
mac os x terminal batch rename
I have essentially the same question, but I'm wanting to do this and change the folder path when renaming. Here is what I asked:
Would any of these solutions work to change underscores to a folder path? For example, I have mbox files on one level that need to be nested, such as:
TopLevel_NextLevel_mbox
TopLevel_NextLevel_FinalLevel_mbox
I'd like to automatically put these in a hierarchy like so:
TopLevel/NextLevel/mbox
TopLevel/NextLevel/FinalLevel/mbox
Can this be done? When I try simple replacement with "/", I get this:
fred$ for f in *_mbox; do mv "$f" "${f/_//}"; done
mv: rename TopLevel_NextLevel_mbox to TopLevel/NextLevel_mbox: No such file or directory
Looks like it just tries to sub in the "/", but then gets confused because there is no current folder TopLevel w/ NextLevel_mbox inside it...
Thanks,
Fred
The basics of making this work starts with the process of creating an array from the current directories that contain *mbox. Each array key then contains the resulting delimited word found between the underscores:
TopLevel_NextLevel_mbox
Is transformed into an array like this:
( TopLevel, NextLevel, mbox )
From there we create the first directory TopLevel then perform a cd followed by mkdir on the next key — repeating the process until there are no more keys. By doing this each array key creates a new nested directory (as a bonus it also copies any data from the original directory into the new one whilst keeping it's structure).
Create Nested Folders from Original
#!/bin/bash
DIR=$PWD
for f in *mbox
do cd $DIR
if [[ -d $f ]]; then
ARR=(${f//_/ }); n=0
for i in "${ARR[#]}"
do echo $n
if [[ $n -eq 0 ]]; then
mkdir -p $i && cp -R $f/* $i && cd $_
else
mkdir -p $i && cd $_
fi
let n++
done
fi
done
Pseudo One-liner
DIR=$PWD; for f in *mbox; do cd $DIR; if [[ -d $f ]]; then ARR=(${f//_/ }); n=0; for i in "${ARR[#]}"; do if [[ $n -eq 0 ]]; then mkdir -p $i && cp -R $f/* $i && cd $_; else mkdir -p $i && cd $_; fi; let n++; done; fi; done
This is the same exact script as the one above it, however, it's formatted to be one line. * The script leaves the original directories intact (I'll leave the exercise of removing them up to the OP).
I have a file that contains some keywords and I intend to create subdirectories into the same directory of the same keyword using a bash script. Here is the code I am using but it doesn't seem to be working.
I don't know where I have gone wrong. Help me out
for i in `cat file.txt`
do
# if [[ ! -e $path/$i ]]; then
echo "creating" $i "directory"
mkdir $path/$i
# fi
grep $i file >> $path/$i/output.txt
done
echo "created the files in "$path/$TEMP/output.txt
You've gone wrong here, and you've gone wrong here.
while read i
do
echo "Creating $i directory"
mkdir "$path/$i"
grep "$i" file >> "$path/$i"/output.txt
done < file.txt
echo "created the files in $path/$TEMP/output.txt"
78mkdir will refuse to create a directory, if parts of it do not exist.
e.g. if there is no /foo/bar directory, then mkdir /foo/bar/baz will fail.
you can relax this a bit by using the -p flag, which will create parent directories if necessary (in the example, it might create /foo and /foo/bar).
you should also use quotes, in case your paths contain blanks.
mkdir -p "${path}/${i}"
finally, make sure that you are actually allowed to create directories in $path
I'm working on a shell script which is a menu-driven interface.
one of the options asks user to enter a list of optional files or directories and then the code will delete them after checking if they are valid files and directories.
the goal of the code is understanding of menu-driven interface.
my problem is reading the list of files and directory names.
It seems that sh does not support array. what is the solution for this problem?
2) echo "enter the name of files want to be deleted: "
read files
.
.
;;
2) echo "enter the name of files want to be deleted"
read a
for CHARACTER in $a ; do
if [ -f $CHARACTER -o -d $CHARACTER ] ; then
rm -ir $CHARACTER
else
echo "$CHARACTER is not a valid file or directory"
fi
done
;;