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)
Related
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
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
As I was deleting many obsolete file trees on a Linux machine I was wondering if there is an easy way to remove files recursively while prompting only on directories.
I could use rm -ri but there some much files that it would be really annoying to answer for every one of them. What really matter to me is being prompted on folders to have more control on what happens.
I am not a bash expert so I am asking if there is a simple way to do this.
Here is my attempt with a long bash script:
#!/bin/bash
promptRemoveDir()
{
fileCount=$(ls -1 $1 | wc -l)
prompt=1
while [ $prompt == 1 ]
do
read -p "remove directory: $1($fileCount files) ? [yl]: " answer
case $answer in
[yY])
rm -r $1
prompt=0
;;
l)
echo $(ls -A $1)
;;
*)
prompt=0
;;
esac
done
}
removeDir()
{
if [ "$(ls -A $1)" ]
then dirs=$(find $1/* -maxdepth 0 -type d)
fi
if [[ -z $dirs ]]
then
promptRemoveDir $1
else
for dir in $dirs
do
removeDir $dir
done
promptRemoveDir $1
fi
}
for i in $*
do
if [ -d $i ]
then
removeDir $i
else
rm $i
fi
done
If i understand your question properly this should work
Dirs=$(find . -type d)
Removes just the files in the directories specified
for i in "$Dirs"; do read -p "Delete files in "$i": ";if [[ $REPLY == [yY] ]]; then find $i -maxdepth 1 -type f | xargs -0 rm ; fi ;done
If you want to delete the folders as well, this will read from lowest directory(none below it) upwards.
for i in $(echo "$Dirs" | sed '1!G;h;$!d' ); do read -p "Delete files in $i: ";if [[ $REPLY == [yY] ]]; then rm -r "$i"; fi ;done
Here's a simplified version from me. There's no need to use ls and find.
#!/bin/bash
shopt -s nullglob
shopt -s dotglob
function remove_dir_i {
local DIR=$1 ## Optional. We can just use $1.
local SUBFILES=("$DIR"/*) FILE
for (( ;; )); do
read -p "Remove directory: $DIR (${#SUBFILES[#]} files)? [YNLQ]: "
case "$REPLY" in
[yY])
echo rm -fr "$DIR"
return 0
;;
[nN])
for FILE in "${SUBFILES[#]}"; do
if [[ -d $FILE ]]; then
remove_dir_i "$FILE" || return 1
# else
# ## Apparently we skip deleting a file. If we do this
# ## we could actually simplify the function further
# ## since we also delete the file at first loop.
# # echo "Removing file \"$FILE.\""
# # rm -f "$FILE"
fi
done
return 0
;;
[lL])
printf '%s\n' "${SUBFILES[#]}"
;;
[qQ])
return 1
;;
# *)
# echo "Please answer Y(es), N(o), L(ist) or Q(uit)."
# ;;
esac
done
}
for FILE; do
if [[ -d $FILE ]]; then
remove_dir_i "$FILE"
else
# echo "Removing file \"$FILE.\""
echo rm -f "$FILE"
fi
done
Remove echo from rm commands when you're sure it's working already. Test:
rm -f /tmp/tar-1.27.1/ABOUT-NLS
rm -f /tmp/tar-1.27.1/acinclude.m4
rm -f /tmp/tar-1.27.1/aclocal.m4
rm -f /tmp/tar-1.27.1/AUTHORS
Remove directory: /tmp/tar-1.27.1/build-aux (12 files)? [YNLQ]: n
Remove directory: /tmp/tar-1.27.1/build-aux/snippet (5 files)? [YNLQ]: n
rm -f /tmp/tar-1.27.1/ChangeLog
rm -f /tmp/tar-1.27.1/ChangeLog.1
rm -f /tmp/tar-1.27.1/config.h.in
rm -f /tmp/tar-1.27.1/configure
rm -f /tmp/tar-1.27.1/configure.ac
rm -f /tmp/tar-1.27.1/COPYING
Remove directory: /tmp/tar-1.27.1/doc (25 files)? [YNLQ]: n
Remove directory: /tmp/tar-1.27.1/gnu (358 files)? [YNLQ]: n
Remove directory: /tmp/tar-1.27.1/gnu/uniwidth (2 files)? [YNLQ]: n
rm -f /tmp/tar-1.27.1/INSTALL
Remove directory: /tmp/tar-1.27.1/lib (19 files)? [YNLQ]:
...
Actually I just came upon the -depth option of the find command that is exactly what I was looking for. I can't believe I just missed that:
-depth Process each directory's contents before the directory itself. The -delete action also implies -depth.
So similar to #Jidder's code, I can write this:
dirs=$(find ./test_script -depth -type d); for i in $dirs; do read -p "Delete files in $i? " REPLY; if [[ $REPLY == [yY] ]]; then rm -r $i; fi; done;
And for more readability:
dirs=$(find ./test_script -depth -type d)
for i in $dirs
do
read -p "Delete files in $i? " REPLY
if [[ $REPLY == [yY] ]]
then rm -r $i
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.