Trying to implement CASE in my shell script - linux

I'm trying to add options to my little safe delete script. For example, I can do ./sdell -s 100 and it will delete files with size above 100 kbs. Anyway, I'm having problems with my safe guard function.
#!/bin/bash
#Purpose = Safe delete
#Created on 20-03-2018
#Version 0.8
#jesus,i'm dumb
#START
##Constants##
dir="/home/cunha/LIXO"
#check to see if the imputs are a files#
for input in "$#"; do
if ! [ -e "$input" ]; then
echo "Input is NOT a file!"
exit 1
fi
done
###main###
case $1 in
-r) echo "test option -r"
;;
*) if [[ -f "$dir/$fwe.tar.bz2" ]]; then
echo "File already exists."
if [[ "$file" -nt "$2" ]]; then
echo "Removing older file." && rm "$dir"/"$fwe.tar.bz2" && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$d$
fi
else
echo "Ziping it and moving." && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$dir"
fi
done
;;
esac
The problem is when I call ./sdell -r file1.txt, it says that the input is not a file.
Here is the script without the case, 100% working before having options.
#!/bin/bash
#Purpose = Safe delete
#Created on 20-03-2018
#Version .7
#START
##Constants##
dir="/home/cunha/LIXO"
#check to see if the imputs are a files#
for input in "$#"; do
if ! [ -e "$input" ]; then
echo "Input is NOT a file!"
exit 0
fi
done
###main###
##Cycle FOR so the script accepts multiple file inputs##
for file in "$#"; do
fwe="${file%.*}"
#IF the input file already exist in LIXO#
if [[ -f "$dir/$fwe.tar.bz2" ]]; then
echo "File already exists."
#IF the input file is newer than the file thats already in LIXO#
if [[ "$file" -nt "$2" ]]; then
echo "Removing older file." && rm "$dir"/"$fwe.tar.bz2" && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$d$
fi
else
echo "Ziping it and moving." && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$dir"
fi
done

The message you are seeing is unrelated to the case..esac, construct, it is printed by this section:
for input in "$#"; do
if ! [ -e "$input" ]; then
echo "Input is NOT a file!"
exit 1
fi
done
which expands all command-line parameters ($#), including the "-r", and exits the script because "-r" is not a file. The case..esac is never reached. You can run your script with
bash -x file.sh -r test
so that you can see exactly which lines are being executed.
The snippet below probably does what you want, processing all arguments sequentially:
#!/bin/bash
while [ ! -z $1 ]; do
case $1 in
-r) echo "option R"
;;
-f) echo "option F"
;;
*) if [ -f $1 ]; then echo "$1 is a file." ; fi
;;
esac
shift
done

Consider checking if -r has been passed before trying other options and use shift if it was:
#!/usr/bin/env sh
dir="/home/cunha/LIXO"
case $1 in
-r) echo "test option -r"
shift
;;
esac
#check to see if the imputs are a files#
for input in "$#"; do
echo current input: "$input"
if ! [ -e "$input" ]; then
echo "Input $input is NOT a file!"
exit 1
fi
done
if [[ -f "$dir/$fwe.tar.bz2" ]]; then
echo "File already exists."
if [[ "$file" -nt "$2" ]]; then
echo "Removing older file..."
# add stuff
fi
else
echo "Ziping it and moving."
# add stuff
fi

Related

Wrote a bash code for UNIX to act as recyclebin. Stuck at passing argument to test function

I wrote a code for recyclebin file for UNIX. And when i bash recyclebin -i filename,
it reads the getopts passed before the filename and after that it calls test function
and enter into the test function but doesnt enter the for loop inside the function. The program is not going anywhere from there, i am stuck there. Any help what am i doing wrong there
#!bin/bash
### 2. creating directory ~/recyclebin in home if not present
if [[ ! -d $HOME/recyclebin ]]
then
mkdir $HOME/recyclebin
#touch $HOME/.restore.info
fi
if [ -d $HOME/recyclebin ] && [ ! -e $HOME/.restore.info ]
then
touch $HOME/.restore.info
fi
### 4. Test Conditions
function test() {
for arg in $*
do
file=$arg
###No Filename Provided Error
if [ $# -eq 0 ]
then
echo "missing operand"
file="Error"
exit 1
###File Does Not Exist Error
elif [ ! -e $file ]
then
echo "Cannot delete $file: No such file exists"
file="Error"
exit 2
###Directory Name provided
elif [ -d $file ]
then
echo "Cannot delete $file : Not a file."
file="Error"
exit 3
###cannot delete any argument with recycle in it
elif [[ "${file}" =~ .*"recycle".* ]]
then
echo "Attempting to delete recycle – operation aborted"
file="Error"
exit 4
fi
processRequest
done
}
processRequest(){
echo "Entered processRequest"
if [ $noChoice = true ]
then
recycle
elif [ $iOption = true ] && [ $vOption = true ]
then
read -p "recycle: remove file '$file' ? [y/n] " response
case $response in
[yY]*)
recycle
echo "recycle: '$file' is removed";;
*)
continue ;;
esac
elif [ $iOption = true ]
then
read -p "recycle: remove file '$file' ? [y/n] " response
case $response in
[yY]*)
recycle
echo "recycle: '$file' is removed";;
*)
continue ;;
esac
elif [ $vOption = true ]
then
recycle
echo "removed '$file' to the Recycle Bin"
fi
}
### Assigning the values of flename, absolute path and inode to variable
### name, absolute_path and inode before sending it to recyclebin folder
recycle() {
# If no errors mv the file to the recyclebin folder and add to .restore.info file.
if [ $file != "Error" ] ; then
name=$(basename ${file}) #bcz file can be entered with full path
path_cropped=$(readlink -e ${i} | cut -d"/" -f6-)
path=$(dirname $(readlink -e ${i} | cut -d"/" -f6-))
absolute_path="${HOME}/${path_cropped}"
inode=$(stat -c '%i' ${file})
filename=$name"_"$inode
echo $newfilename:$fixedPath >> $HOME/.restore.info
mv $file $recyclepath/$newfilename
fi
}
###--------------------Starts from here after checking dir if exists or not--------------------------------------
###Declaring variables which further fulfills condition to act as i, v, r commands
###as i = iOption; v = vOption; no command = noChoice and recycle files recursively
### = r or R
noChoice=true
iOption=false
vOption=false
removeFolder=false
while getopts ivrRvi opt
do
case $opt in
iv)
iOption=true
vOption=true
noChoice=false
break;;
vi)
iOption=true
vOption=true
noChoice=false
break;;
i)
iOption=true
noChoice=false
break;;
v)
vOption=true
noChoice=false
break;;
r)
removeFolder=true
break;;
R)
removeFolder=true
break;;
esac
done
shift $(($OPTIND - 1))
### CAlling the test function to test all the mentioned conditioned inside the test function
test
--------------------------------------------------------------------------
``` ```````````````````````````````````````````````````````````````
**Solved code**
``` ```````````````````````````````````````````````````````````````
#!bin/bash
### 2. creating directory ~/recyclebin in home if not present
if [[ ! -d $HOME/recyclebin ]]
then
mkdir $HOME/recyclebin
#touch $HOME/.restore.info
fi
### Creating .restore.info file if file is not yet present in
### home directory
if [ -d $HOME/recyclebin ] && [ ! -e $HOME/.restore.info ]
then
touch $HOME/.restore.info
fi
if [ $# -eq 0 ]
then
echo "recycle: missing operand"
exit 1
fi
### 4. Test Conditions to check
testCondition() {
for arg in "$#"
do
file=$arg
###File Does Not Exist Error
if [ ! -e "$file" ]
then
echo "recycle: Cannot delete $file: No such file exists"
file="Error"
exit 2
###Directory Name provided
elif [ -d $file ]
then
echo "recycle: Cannot delete $file : Is a directory."
file="Error"
exit 3
###cannot delete any argument with recycle in it
elif [[ "${file}" =~ .*"recycle".* ]]
then
echo "recycle: Attempting to delete – operation aborted"
file="Error"
exit 4
fi
processRequest $file
done
}
processRequest(){
if [[ "$noChoice" = true ]]
then
recycle $file
elif [ "$iOption" = true ] && [ "$vOption" = true ]
then
echo "recycle: remove file '$file' ? [y/n] "
read response
case $response in
[yY]*)
recycle $file
echo "recycle: '$file' is removed";;
*)
continue ;;
esac
elif [ "$iOption" = true ]
then
read -p "recycle: remove file '$file' ? [y/n] " response
case $response in
[yY]*)
recycle $file
echo "recycle: '$file' is removed";;
*)
continue ;;
esac
elif [ "$vOption" = true ]
then
recycle $file
echo "removed '$file' to the Recycle Bin"
fi
}
### Assigning the values of flename, absolute path and inode to variable
### name, absolute_path and inode before sending it to recyclebin folder
recycle() {
# If no errors mv the file to the recyclebin folder and add to .restore.info file.
if [[ ! $file == "Error" ]]
then
name=$(basename $file) #bcz file can be entered with full path
path_cropped=$(readlink -e $file | cut -d"/" -f6-)
absolute_path="${HOME}/${path_cropped}"
inode=$(stat -c '%i' $file)
filename=$name"_"$inode
echo $filename:$absolute_path >> $HOME/.restore.info # saving filename:path/to/file in .restore.info
mv $file $HOME/recyclebin/$filename # movinf file as filename_inode to recyclebin
fi
}
recFolder() {
for arg in $*
do
dirPath=$(readlink -e "$arg")
if [ -d "$dirPath" ] # check if the current argument IS a directory
then
if [[ $(ls "$dirPath" | wc -l) -eq 0 ]] #check to see if current directory is empty
then
rmdir $dirPath
else #if not an empty directory
findCurPathList=$(find $dirPath) # to find all the subdirectories and files list inside directory
for file in $findCurPathList
do
if [[ -f "$file" ]] #if it is a file
then
processRequest "$file"
fi
done
#-----------------------------------------------Now in previous loop, once all the files are removed and saved in
#recyclebinfolder and in .recycle.info file. Now delete the
#empty subdirectories in second loop
for args1 in $findCurPathList
do
if [[ -d $args1 ]]
then
if [[ $(ls $args1 | wc -l) -eq 0 ]]
then
rmdir $args1
fi
fi
done
#if [[ $(ls "$arg" | wc -l) -eq 0 ]] #check to see if current directory is empty
#then
# rmdir $dirPath
#fi
fi
else # if NOT a directory then this executes
echo "recycle: not a directory"
fi
done
}
###--------------------Starts from here, after checking dir if exists or not--------------------------------------
###Declaring variables which further fulfills condition to act as i, v, r commands
###as i = iOption; v = vOption; no command = noChoice and recycle files recursively
### = r or R
noChoice=true
iOption=false
vOption=false
removeFolder=false
while getopts ivrR opt
do
case $opt in
i)
iOption=true
noChoice=false;;
v)
vOption=true
noChoice=false;;
r)
removeFolder=true;;
R)
removeFolder=true;;
*)
echo "recycle: invalid input"
exit 1;;
esac
done
shift $(($OPTIND - 1))
### Now checking if -r or -R is passed as an argument and the passed in file argument
### is either directory or existing file to continue
if [[ "$removeFolder" == true ]]
then
for arg in "$#"
do
inputArg=$arg
if [[ -d $inputArg || -e $inputArg ]] # if the user pass -r request but the argument could either be
#a directory o a file not directory
then
argList=$(find "$inputArg")
recFolder "$argList"
else
echo "recycle: $inputArg does not exist!"
fi
done
else
### CAlling the test function to test all the mentioned conditioned inside the test function
testCondition "$#"
fi

Hide sub directories in while loop

I need to make a script that creates soft/symbolic links, but should also detect if links already exist in the '~/linkedfiles' directory, but the problem is that $file also contains sub directories. ($file will look like this: '~/realfiles/files/file23.gz', but I need 'file23.gz' only.) So my question is, how do I remove the sub directories in $file?
Here is some code as an example:
for file in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
ln -s $file ~/linkedfiles
if [ $? -ne 0 ]; then
echo "[FAIL] Linking of $file failed!"
else
echo "[SUCCESS] $file successfully linked."
fi
done
It appears that you want the base name of the path. There are two ways to do that — the classic reliable way is with the basename command, and the modern not-always-reliable way is with a shell parameter expansion.
for file in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
ln -s "$file" "~/linkedfiles/$(basename "$file")"
if [ $? -ne 0 ]
then echo "[FAIL] Linking of $file failed!"
else echo "[SUCCESS] $file successfully linked."
fi
done
Or:
for file in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
ln -s "$file" "~/linkedfiles/${file##*/}"
if [ $? -ne 0 ]
then echo "[FAIL] Linking of $file failed!"
else echo "[SUCCESS] $file successfully linked."
fi
done
In both scripts, the file ~/realfiles/files/file23.gz will be linked to ~/linkedfiles/file23.gz, which is what I think you are after (though there is room to improve the clarity of the question, such as by citing the desired result for the sample file name).
Slight modification of original script would do
for filename in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
[[ -h "~/linkedfiles/${var##*/}" ]] && continue
ln -s "$filename" ~/linkedfiles/
if [ $? -ne 0 ]
then
echo "[FAIL] Linking of $filename failed!"
else
echo "[SUCCESS] $filename successfully linked."
fi
done
Notes
The -h option with if [ checks ] if the file is a symbolic link
${var##*/} gives you the base name. Check [ parameter expansion ].
targetdir="$HOME/linkedfiles"
for filename in $HOME/realfiles/files/*.gz; do
# if filename is a regular file then ...
if [[ -f "${filename}" ]]; then
# if softlink does not exist in target then link it
if [[ ! -h "${targetdir}/${filename##*/}" ]]; then
echo "Linking ${filename}"
ln -s "${filename}" "${targetdir}"
(( $? == 0 )) && echo 'Link created' || echo 'Create link fails'
else
echo "Skipping because ${filename##*/} exists in ${targetdir}"
fi
fi
done

Shell-Script:Create File List Based on Certain Charachter

I need to create one file list for below files:
APPLE_001
APPLE_002
BBB_004
APPLE_003
I need to create file_list only for
APPLE_001
APPLE_002
APPLE_003
Thanks,
Ipsita
Your specifications are not that narrow, but here a bash script that match your request :
#!/bin/bash
if [[ $# < 2 ]]
then
echo "[ERROR] this script expects two arguments, input file and output file" >&2
exit 1
fi
input_file=$1
output_file=$2
if [[ ! -f $input_file ]]
then
echo "[ERROR] your input file '$input_file' is missing" >&2
exit 1
fi
if [[ -f $output_file ]]
then
echo "[ERROR] your output file '$ouput_file' already exists please move it away" >&2
exit 1
fi
while read LINE
do
if [[ $LINE =~ APPLE_[0-9]+ ]]
then
echo $LINE >> $output_file
else
echo "'$LINE' does not match expected pattern, skip it"
fi
done < $input_file
if [[ -f $ouput_file ]]
then
echo "'$output_file' generated."
else
echo "[WARNING] no pattern found in '$input_file' no file generated"
fi
make it executable ( chmod +x ./list_starting_with.sh )
run it with ./list_starting_with.sh file_in.txt file_out.txt

Exit a script when there is file with certain name but ignoring a directory with the same name

If a file called “output” already exists, rather than a directory, the script
should display an error and quit.
here is my code so far
for file in *
do
if [ ! -f output ]
then echo "error"
exit 1
fi
done
for file in *; do
if [ "$file" = "output" -a -f "$file" ]; then
echo "error"
exit 1
fi
done
Or
for file in *; do
if [ "$file" = "output" ] && [ -f "$file" ]; then
echo "error"
exit 1
fi
done
And with bash, this one's preferred:
for file in *; do
if [[ $file == output && -f $file ]]; then
echo "error"
exit 1
fi
done
If you want to check if the filename contains the word, not just exactly matches it:
for file in *; do
if [[ $file == *output* && -f $file ]]; then
echo "error"
exit 1
fi
done
Why are we processing every file in the subdirectory? Very Odd.
if [ -f output ]; then
echo "'output exists and is a file"
exit 1
fi
The test command (which is also [) (and is also built-in to most shells (see bash man page too) ), responds with a TRUE response for -f output only when output is a file. You can check if it's a directory with -d.
touch something
if [ -f something ]; then echo "something is a file"; fi
if [ -d something ]; then echo "something is not a file"; fi
rm something
mkdir something
if [ -f something ]; then echo "something is not a subdir"; fi
if [ -d something ]; then echo "something is a subdir"; fi
rmdir something
If you try those commands, you'll get:
something is a file
something is a subdir
No point in iterating through the entire directory contents if you're just looking if a specific file/dir exists.

Shortest way to swap two files in bash

Can two files be swapped in bash?
Or, can they be swapped in a shorter way than this:
cp old tmp
cp curr old
cp tmp curr
rm tmp
$ mv old tmp && mv curr old && mv tmp curr
is slightly more efficient!
Wrapped into reusable shell function:
function swap()
{
local TMPFILE=tmp.$$
mv "$1" $TMPFILE && mv "$2" "$1" && mv $TMPFILE "$2"
}
Add this to your .bashrc:
function swap()
{
local TMPFILE=tmp.$$
mv "$1" $TMPFILE
mv "$2" "$1"
mv $TMPFILE "$2"
}
If you want to handle potential failure of intermediate mv operations, check Can Bal's answer.
Please note that neither this, nor other answers provide an atomic solution, because it's impossible to implement such using Linux syscalls and/or popular Linux filesystems. For Darwin kernel, check exchangedata syscall.
tmpfile=$(mktemp $(dirname "$file1")/XXXXXX)
mv "$file1" "$tmpfile"
mv "$file2" "$file1"
mv "$tmpfile" "$file2"
do you actually want to swap them?
i think its worth to mention that you can automatically backup overwritten file with mv:
mv new old -b
you'll get:
old and old~
if you'd like to have
old and old.old
you can use -S to change ~ to your custom suffix
mv new old -b -S .old
ls
old old.old
using this approach you can actually swap them faster, using only 2 mv:
mv new old -b && mv old~ new
Combining the best answers, I put this in my ~/.bashrc:
function swap()
{
tmpfile=$(mktemp $(dirname "$1")/XXXXXX)
mv "$1" "$tmpfile" && mv "$2" "$1" && mv "$tmpfile" "$2"
}
You could simply move them, instead of making a copy.
#!/bin/sh
# Created by Wojtek Jamrozy (www.wojtekrj.net)
mv $1 cop_$1
mv $2 $1
mv cop_$1 $2
http://www.wojtekrj.net/2008/08/bash-script-to-swap-contents-of-files/
This is what I use as a command on my system ($HOME/bin/swapfiles). I think it is relatively resilient to badness.
#!/bin/bash
if [ "$#" -ne 2 ]; then
me=`basename $0`
echo "Syntax: $me <FILE 1> <FILE 2>"
exit -1
fi
if [ ! -f $1 ]; then
echo "File '$1' does not exist!"
fi
if [ ! -f $2 ]; then
echo "File '$2' does not exist!"
fi
if [[ ! -f $1 || ! -f $2 ]]; then
exit -1
fi
tmpfile=$(mktemp $(dirname "$1")/XXXXXX)
if [ ! -f $tmpfile ]; then
echo "Could not create temporary intermediate file!"
exit -1
fi
# move files taking into account if mv fails
mv "$1" "$tmpfile" && mv "$2" "$1" && mv "$tmpfile" "$2"
A somewhat hardened version that works for both files and directories:
function swap()
{
if [ ! -z "$2" ] && [ -e "$1" ] && [ -e "$2" ] && ! [ "$1" -ef "$2" ] && (([ -f "$1" ] && [ -f "$2" ]) || ([ -d "$1" ] && [ -d "$2" ])) ; then
tmp=$(mktemp -d $(dirname "$1")/XXXXXX)
mv "$1" "$tmp" && mv "$2" "$1" && mv "$tmp"/"$1" "$2"
rmdir "$tmp"
else
echo "Usage: swap file1 file2 or swap dir1 dir2"
fi
}
This works on Linux. Not sure about OS X.
Hardy's idea was good enough for me.
So I've tried my following two files to swap "sendsms.properties", "sendsms.properties.swap".
But once I called this function as same argument "sendsms.properties" then this file deleted. Avoiding to this kind of FAIL I added some line for me :-)
function swp2file()
{ if [ $1 != $2 ] ; then
local TMPFILE=tmp.$$
mv "$1" $TMPFILE
mv "$2" "$1"
mv $TMPFILE "$2"
else
echo "swap requires 2 different filename"
fi
}
Thanks again Hardy ;-)
using mv means you have one fewer operations, no need for the final rm, also mv is only changing directory entries so you are not using extra disk space for the copy.
Temptationh then is to implementat a shell function swap() or some such. If you do be extremly careful to check error codes. Could be horribly destructive. Also need to check for pre-existing tmp file.
One problem I had when using any of the solutions provided here: your file names will get switched up.
I incorporated the use of basename and dirname to keep the file names intact*.
swap() {
if (( $# == 2 )); then
mv "$1" /tmp/
mv "$2" "`dirname $1`"
mv "/tmp/`basename $1`" "`dirname $2`"
else
echo "Usage: swap <file1> <file2>"
return 1
fi
}
I've tested this in bash and zsh.
*So to clarify how this is better:
If you start out with:
dir1/file2: this is file2
dir2/file1: this is file1
The other solutions would end up with:
dir1/file2: this is file1
dir2/file1: this is file2
The contents are swapped but the file names stayed. My solution makes it:
dir1/file1: this is file1
dir2/file2: this is file2
The contents and names are swapped.
Here is a swap script with paranoid error checking to avoid unlikely case of a failure.
if any of the operations fail it's reported.
the path of the first argument is used for the temp path (to avoid moving between file-systems).
in the unlikely case the second move fails, the first is restored.
Script:
#!/bin/sh
if [ -z "$1" ] || [ -z "$2" ]; then
echo "Expected 2 file arguments, abort!"
exit 1
fi
if [ ! -z "$3" ]; then
echo "Expected 2 file arguments but found a 3rd, abort!"
exit 1
fi
if [ ! -f "$1" ]; then
echo "File '$1' not found, abort!"
exit 1
fi
if [ ! -f "$2" ]; then
echo "File '$2' not found, abort!"
exit 1
fi
# avoid moving between drives
tmp=$(mktemp --tmpdir="$(dirname '$1')")
if [ $? -ne 0 ]; then
echo "Failed to create temp file, abort!"
exit 1
fi
# Exit on error,
mv "$1" "$tmp"
if [ $? -ne 0 ]; then
echo "Failed to to first file '$1', abort!"
rm "$tmp"
exit 1
fi
mv "$2" "$1"
if [ $? -ne 0 ]; then
echo "Failed to move first file '$2', abort!"
# restore state
mv "$tmp" "$1"
if [ $? -ne 0 ]; then
echo "Failed to move file: (unable to restore) '$1' has been left at '$tmp'!"
fi
exit 1
fi
mv "$tmp" "$2"
if [ $? -ne 0 ]; then
# this is very unlikely!
echo "Failed to move file: (unable to restore) '$1' has been left at '$tmp', '$2' as '$1'!"
exit 1
fi
Surely mv instead of cp?
mv old tmp
mv curr old
mv tmp curr
I have this in a working script I delivered. It's written as a function, but you would invoke it
d_swap lfile rfile
The GNU mv has the -b and the -T switch. You can deal with directories using the -T
switch.
The quotes are for filenames with spaces.
It's a bit verbose, but I've used it many times with both files and directories. There might be cases where you would want to rename a file with the name a directory had, but that isn't handled by this function.
This isn't very efficient if all you want to do is rename the files (leaving them where they are), that is better done with a shell variable.
d_swap() {
test $# -eq 2 || return 2
test -e "$1" || return 3
test -e "$2" || return 3
if [ -f "$1" -a -f "$2" ]
then
mv -b "$1" "$2" && mv "$2"~ "$1"
return 0
fi
if [ -d "$1" -a -d "$2" ]
then
mv -T -b "$1" "$2" && mv -T "$2"~ "$1"
return 0
fi
return 4
}
This function will rename files. It uses a temp name (it puts a dot '.' in front of the name) just in case the files/directories are in the same directory, which is usually the case.
d_swapnames() {
test $# -eq 2 || return 2
test -e "$1" || return 3
test -e "$2" || return 3
local lname="$(basename "$1")"
local rname="$(basename "$2")"
( cd "$(dirname "$1")" && mv -T "$lname" ".${rname}" ) && \
( cd "$(dirname "$2")" && mv -T "$rname" "$lname" ) && \
( cd "$(dirname "$1")" && mv -T ".${rname}" "$rname" )
}
That is a lot faster (there's no copying, just renaming). It is even uglier. And it will rename anything: files, directories, pipes, devices.

Resources