I am trying to create a basic recycle bin concept in a VM using bash scripting. It will need to delete files that have been entered and place them into a directory that is created and save the path(origin) to a log file to be later used in a restore function.
I will start off with my delete/recycle code which I believe works just fine but seems kind of untidy/contains redundant code:
#!/bin/sh
if [ ! -d ~/recycle ]
then mkdir ~/recycle
fi
if [ ! -d ~/recycle/recycle_log ]
then mkdir ~/recycle/recycle_log
fi
if [ ! -d ~/recycle/recycle_bin ]
then mkdir ~/recycle/recycle_bin
fi
if [ -d ~/recycle ]
then
echo "$(readlink -f "$1")" >> "$HOME/recycle/recycle_log/log_file" && mv "$1" "$HOME/recycle/recycle_bin"
echo "$(readlink -f "$2")" >> "$HOME/recycle/recycle_log/log_file" && mv "$2" "$HOME/recycle/recycle_bin"
echo "$(readlink -f "$3")" >> "$HOME/recycle/recycle_log/log_file" && mv "$3" "$HOME/recycle/recycle_bin"
echo "$(readlink -f "$4")" >> "$HOME/recycle/recycle_log/log_file" && mv "$4" "$HOME/recycle/recycle_bin"
fi
#end
Thereafter what I have for my restore script is as follows:
#!/bin/sh
cd "$HOME/recycle/recycle_bin" || exit 1
mv -i "$(grep "$1" "$HOME/recycle/recycle_log")"
I imagine this is somewhat close to what I need to return any deleted file stored in the log/recycle bin to be restored to its origin but the error I am getting is:
mv: missing destination file operand after `'
Any thoughts on where I'm going wrong?
Try this:
recycle.sh
#!/bin/sh
set -e
check_dir() {
[ ! -d $1 ] || return 0
mkdir --parents $1
}
check_dir "${HOME}/recycle/recycle_bin"
touch "${HOME}/recycle/recycle_log"
for file in "$#"; do
echo "$(readlink -f "$file")" >> "${HOME}/recycle/recycle_log"
mv "$file" "${HOME}/recycle/recycle_bin"
done
#end
restore.sh
#!/bin/sh
set -e
cd "${HOME}/recycle/recycle_bin" || exit 1
for name in "$#"; do
file=$(grep "\/${name}\$" "${HOME}/recycle/recycle_log")
mv -i $name "$file"
sed -i "/\/${name}\$/ d" "${HOME}/recycle/recycle_log"
done
Some insights:
set -e: Abort on any error, to avoid some if's
$#: The array of arguments ($1, $2...)
[ ! -d $1 ] || return 0: Since we are using set -e, do not fail if the directory exists
grep "\/${name}\$" ...: Only matches the name at the end of the path
sed -i: sed in-place editing to remove the line
Related
I have a car stereo that reads USB sticks, but the stereo's "random" function only works on one folder, so I want to move all files to a parent folder, "STICK/music", but to have the Artist, and Album if any, added to the filename.
But what I've done is sort of hard-coded, and I'd like to be able to use different sticks.
I would like to have these ("RHYTHMBLUES" is the name of the stick):
/media/homer/RHYTHMBLUES/music/Artist/Album/name1.mp3
/media/homer/RHYTHMBLUES/music/Artist/Album/name2.mp3
/media/homer/RHYTHMBLUES/music/Artist/name3.mp3
/media/homer/RHYTHMBLUES/music/Artist/name4.mp3
renamed with folder names and moved to "music":
/media/homer/RHYTHMBLUES/music/Artist-Album-name1.mp3
/media/homer/RHYTHMBLUES/music/Artist-Album-name2.mp3
/media/homer/RHYTHMBLUES/music/Artist-name3.mp3
/media/homer/RHYTHMBLUES/music/Artist-name4.mp3
I thought of having a bash command to call "doit":
find . -type d -exec sh -c "cd \"{}\" ; /home/homer/doit \"{}\" " \;
and "doit" would contain:
#!/bin/sh -x
echo --------------------------------
pwd
for i in *.mp3
do new=$(printf "${1}/${i}")
# remove the ./ at the beginning of the name
new=`echo "${new}" | cut -c 3-`
# replace folder separators
new=`echo "${new}" | tr '/' '-'`
echo "$i" "/media/homer/RHYTHMBLUES/music/$new"
mv -T "$i" "/media/homer/RHYTHMBLUES/music/$new"
done
doitbaby.sh
#!/bin/sh
moveTo () { # $1=directory, $2=path, $3=targetName
local path="$(realpath "$1")"
for file in "$path"/*; do
if [ -d "$file" -a -n "$3" ]; then
moveTo "$file" "$2" "$3""${file##*/}"-
elif [ -d "$file" ]; then
moveTo "$file" "$path" "${file##*/}"-
elif [ -f "$file" -a "${file##*.}" = 'mp3' ]; then
echo 'FROM: '"$file"
echo 'TO : '"$2"/"$3""${file##*/}"
# mv "$file" "$2"/"$3""${file##*/}" # Uncomment this for real usage.
fi
done
}
removeEmptyDirs () {
local path="$(realpath "$1")"
for file in "$path"/*; do
if [ -d "$file" ]; then
rm -fR "$file"
fi
done
}
moveTo "$1"
# removeEmptyDirs "$1" # Uncomment this for real usage.
Note
I have commented 2 lines in the above script. Test it first before any further real actions.
Call the script like this: (Make sure the path is correct. This is the expected path by this script by the way.)
./doitbaby.sh '/media/homer/RHYTHMBLUES/music/'
I am trying to write a bash/shell script for downloading BuildTools and compiling the jars with it, after that I want to move them to my webserver (/var/www/html/jars/) from within the script at /home/buildtools/
BuildTools downloads/compiles the jars and places them at the base directory, except the vanilla jar, which is located at work/ inside the base directory.
Even though it does download/compile the jars, they aren't all moving to the desired directories.
This is the code I am using at the moment (I think it might all be relevant to provide):
directory="$(pwd)/"
jar="BuildTools.jar"
path="${directory}${jar}"
output="/var/www/html/jars"
version=$1
if [[ ( -d "BuildData" ) || ( -d "Spigot" ) || ( -d "CraftBukkit" ) || ( -d "Bukkit" ) || ( -d "work" ) || ( -d "apache-maven-*" ) || ( -f "BuildTools.log.txt" ) ]]; then
echo "Cleaning up..."
rm -f -r BuildData/ Spigot/ CraftBukkit/ Bukkit/ work/ apache-maven-*/
rm -f BuildTools.log.txt spigot-*.jar craftbukkit-*.jar vanilla-*.jar
fi
if [[ ! -f "${jar}" ]]; then
echo "Downloading BuildTools..."
wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O ${jar}
chmod 775 ${jar}
fi
if [[ ! -z "${version}" ]]; then
echo "Compiling jars for version ${version}..."
java -jar ${jar} --rev ${version}
else
echo "No version specified."
echo "Compiling jars for the latest version..."
java -jar ${jar} --rev latest
fi
if [[ ( -f "${directory}spigot-*.jar" ) || ( -f "${directory}craftbukkit-*.jar" ) ]]; then
echo "Compilation is done. Re-organizing..."
if [[ ! -z "${version}" ]]; then
mv work/minecraft_server.*.jar ${directory}/vanilla-${version}.jar
else
mv work/minecraft_server.*.jar ${directory}/vanilla-latest.jar
fi
echo "Moving jars to the webserver..."
mv ${directory}/vanilla-*.jar ${output}/
mv ${directory}/spigot-*.jar ${output}/
mv ${directory}/craftbukkit-*.jar ${output}/
else
echo "Could not re-organize, the jars hasn't been moved."
fi
Edit:
By executing the command: ./compile.sh 1.8.6
For some reason the jarfiles aren't the right names.
Result of the code.
directory="$(pwd)/"
jar="BuildTools.jar"
path="${directory}${jar}"
output="/var/www/html/jars"
version=$1
if [[ ( -d "BuildData" ) || ( -d "Spigot" ) || ( -d "CraftBukkit" ) || ( -d "Bukkit" ) || ( -d "work" ) || ( -d "apache-maven-*" ) || ( -f "BuildTools.log.txt" ) ]]; then
echo "Cleaning up..."
rm -f -r BuildData/ Spigot/ CraftBukkit/ Bukkit/ work/ apache-maven-*/
rm -f BuildTools.log.txt done.txt spigot-*.jar craftbukkit-*.jar vanilla-*.jar
fi
if [[ ! -f "${jar}" ]]; then
echo "Downloading BuildTools..."
wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O ${jar}
chmod 775 ${jar}
fi
if [[ ! -z "${version}" ]]; then
echo "Compiling jars for version ${version}..."
java -jar ${jar} --rev ${version}
echo "done" > done.txt
else
echo "No version specified. Compiling jars for the latest version..."
java -jar ${jar} --rev latest
echo "done" > done.txt
fi
if [[ -f "done.txt" ]]; then
echo "Compilation is done. Re-organizing..."
for x in $(find ${directory} \( -name "*.jar" \)); do
name=$(basename "${x}" ".jar")
if [[ ${name} =~ ^(spigot|craftbukkit|minecraft_server)(-)?(.*)? ]]; then
fullname="${name}.jar"
echo "Jar found: ${fullname}, Moving to webserver..."
if [[ ! -z "${version}" ]]; then
outpath=${output}/${version}
if [[ ! -d ${outpath} ]]; then
mkdir ${outpath}
fi
mv -f ${x} ${outpath}/${fullname}
else
outpath=${output}/latest
if [[ ! -d ${outpath} ]]; then
mkdir ${outpath}
fi
rm -f -r ${outpath}
mv -f ${x} ${outpath}/${fullname}
fi
fi
done
echo "Jars uploaded."
else
echo "Could not re-organize, the jars hasn't been moved."
fi
And additionally, I would like to only have one file of each uploaded (Vanilla, Spigot, Craftbukkit), how do I do that?
It's because of the parenthesis inside your conditional expression, as well as trying to use globs on the wrong part of the expression (you can't use tests like that with globs).
To fix this you should do something like:
#set nullglob so non-matching globs return an empty string instead of an error
shopt -s nullglob
# assing the globs to arrays, note the quoting
spigotfiles=("${directory}"spigot-*.jar)
bukkitfiles=("${directory}"craftbukkit-*.jar)
# using an array without an index gives us the first item in the array
if [[ -f "$spigotfiles" || -f "$bukkitfiles" ]]; then
...
fi
The same error with parenthesis is in your cleanup-code too, which you can handle by just removing the parens (e.g. [[ -d "dirname" || -d "fooname" ]] )
You should also quote your variable expansions (i.e. "${jar}" etc.) in every case (like in a part of a command).
Also using [[ ! -z $foo ]] is the same as [[ -n $foo ]]
Edit:
Don't do
for x in $(find ${directory} \( -name "*.jar" \)); do
but instead just:
for x in *.jar; do
or if you want only your versioned files, you can do:
for x in *"-${version}.jar"; do
You still have the parens in the cleanup check, see the answer above for that.
And in the end of the script these lines are counterproductive:
if [[ ! -d ${outpath} ]]; then
mkdir ${outpath}
fi
rm -f -r ${outpath}
mv -f ${x} ${outpath}/${fullname}
As you first create a dir, then remove it and then try to move things into it (hint: remove the rm line)
I'm having a bit of trouble running this script. Every now and then, it will fail to execute a mv command or rm command referenced below. Then the next iteration will feel the full repercussions of that failure. Why is this happening, and how do I control for it? For reference, the syntax phyml -i $f [parameters] outputs the files $f_phyml_tree.txt and $f_phyml_stats.txt into the same directory as $f. I want to get both these files out of that directory while saving the tree somewhere else.
ber="_phyml_tree.txt"
for f in ~/randseqs/aa/*.txt;
do
echo $f
fpath=`echo $f | cut -d'/' -f 6`
if [ ! -f /home/mcb3421_10/phymlout/aa/$fpath$ber ] || [ ! -f /home/mcb3421_10/phymltimer/aa/$fpath ]; then
phyml -i $f -d aa -b 0 -m Blosum62 > ~/blown.txt
grep "Time used" < ~/blown.txt > ~/phymltimer/aa/$fpath
mv /home/mcb3421_10/randseqs/aa/*$ber phymlout/aa
if [ ! -f /home/mcb3421_10/phymlout/aa/$fpath$ber ]; then
echo $f failed to write, check the logfile /home/mcb3421_10/phymllogs/aa/$fpath
fi
rm ~/randseqs/aa/*_stat*
mv ~/blown.txt ~/phymllogs/aa/$fpath
fi
done
for f in ~/randseqs/nuc/*.txt;
do
echo $f
fpath=`echo $f | cut -d'/' -f 6`
if [ ! -f /home/mcb3421_10/phymlout/nuc/$fpath$ber ] || [ ! -f /home/mcb3421_10/phymltimer/nuc/$fpath ]; then
phyml -i $f -d nt -b 0 -m HKY85 > ~/blown.txt
grep "Time used" < ~/blown.txt > ~/phymltimer/nuc/$fpath
mv /home/mcb3421_10/randseqs/nuc/*$ber phymlout/nuc
if [ ! -f /home/mcb3421_10/phymlout/nuc/$fpath$ber ]; then
echo $f failed to write, check the logfile /home/mcb3421_10/phymllogs/nuc/$fpath
fi
rm ~/randseqs/nuc/*_stat*
mv ~/blown.txt ~/phymllogs/nuc/$fpath
fi
done
I have a file called diff.txt. I Want to check whether it is empty.
I wrote a bash script something like below, but I couldn't get it work.
if [ -s diff.txt ]
then
touch empty.txt
rm full.txt
else
touch full.txt
rm emtpy.txt
fi
Misspellings are irritating, aren't they? Check your spelling of empty, but then also try this:
#!/bin/bash -e
if [ -s diff.txt ]; then
# The file is not-empty.
rm -f empty.txt
touch full.txt
else
# The file is empty.
rm -f full.txt
touch empty.txt
fi
I like shell scripting a lot, but one disadvantage of it is that the shell cannot help you when you misspell, whereas a compiler like your C++ compiler can help you.
Notice incidentally that I have swapped the roles of empty.txt and full.txt, as #Matthias suggests.
[ -s file.name ] || echo "file is empty"
[ -s file ] # Checks if file has size greater than 0
[ -s diff.txt ] && echo "file has something" || echo "file is empty"
If needed, this checks all the *.txt files in the current directory; and reports all the empty file:
for file in *.txt; do if [ ! -s $file ]; then echo $file; fi; done
To check if file is empty or has only white spaces, you can use grep:
if [[ -z $(grep '[^[:space:]]' $filename) ]] ; then
echo "Empty file"
...
fi
While the other answers are correct, using the "-s" option will also show the file is empty even if the file does not exist.
By adding this additional check "-f" to see if the file exists first, we ensure the result is correct.
if [ -f diff.txt ]
then
if [ -s diff.txt ]
then
rm -f empty.txt
touch full.txt
else
rm -f full.txt
touch empty.txt
fi
else
echo "File diff.txt does not exist"
fi
Easiest way for checking if file is empty or not:
if [ -s /path-to-file/filename.txt ]
then
echo "File is not empty"
else
echo "File is empty"
fi
You can also write it on single line:
[ -s /path-to-file/filename.txt ] && echo "File is not empty" || echo "File is empty"
#geedoubleya answer is my favorite.
However, I do prefer this
if [[ -f diff.txt && -s diff.txt ]]
then
rm -f empty.txt
touch full.txt
elif [[ -f diff.txt && ! -s diff.txt ]]
then
rm -f full.txt
touch empty.txt
else
echo "File diff.txt does not exist"
fi
[[ -f filename && ! -s filename ]] && echo "filename exists and is empty"
Many of the answers are correct but I feel like they could be more complete
/ simplistic etc. for example :
Example 1 : Basic if statement
# BASH4+ example on Linux :
typeset read_file="/tmp/some-file.txt"
if [ ! -s "${read_file}" ] || [ ! -f "${read_file}" ] ;then
echo "Error: file (${read_file}) not found.. "
exit 7
fi
if $read_file is empty or not there stop the show with exit. More than once I have had misread the top answer here to mean the opposite.
Example 2 : As a function
# -- Check if file is missing /or empty --
# Globals: None
# Arguments: file name
# Returns: Bool
# --
is_file_empty_or_missing() {
[[ ! -f "${1}" || ! -s "${1}" ]] && return 0 || return 1
}
Similar to #noam-manos's grep-based answer, I solved this using cat. For me, -s wasn't working because my "empty" file had >0 bytes.
if [[ ! -z $(cat diff.txt) ]] ; then
echo "diff.txt is not empty"
else
echo "diff.txt is empty"
fi
I came here looking for how to delete empty __init__.py files as they are implicit in Python 3.3+ and ended up using:
find -depth '(' -type f -name __init__.py ')' -print0 |
while IFS= read -d '' -r file; do if [[ ! -s $file ]]; then rm $file; fi; done
Also (at least in zsh) using $path as the variable also breaks your $PATH env and so it'll break your open shell. Anyway, thought I'd share!
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.