remove files and prompt directories only - linux

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;

Related

Find specific file extensions in recursive structure and extract them until no left

I have a recursive multiple folder structures. Inside of there are multiple zip, img files. But when I extracted it, new zip,img files come out again. I want to write a loop for this.
Scan whole recursive folders, find multiple zip files and extract them. After extraction scan folders again if system found new zip files, extract it again and again continue until no left zip file in the folders.
sample.tar.gz
1.img
Thefolder
samplefolder
t.img
2.img
samplefolder2
different.gz
3.img
4.img
There are more folders also under other img files.
I tried to write for loop for this but couldn't work it. After extraction 1.img script getting error.
Error Output:
ID = 277952352
Everything is Ok
Folders: 3
Files: 1
Size: 345424152
Compressed: 671024244
+ rm -f /home/sample/1.img
+ recursiveExtract /home/sample/Thefolder
+ for file in "$path"/*
+ '[' -d /home/sample/Thefolder ']'
+ recursiveExtract /home/sample/Thefolder
Segmentation fault
The code:
#!/bin/bash
set -x
target_file=$1
path="$(realpath "$1")"
recursiveExtract () { # $1=directory
for file in "$path"/*; do
if [ -d "$file" ]; then
recursiveExtract "$file"
elif [ -f "$file" -a "${file##*.}" = 'img' ]; then
7z x $file -o$path -r # variation 2
rm -f "$file" # comment this if you want to keep the zip files.
recursiveExtract "${file%.img}"
fi
b=$(ls $path | grep img | wc -l)
if [[ "$b" -eq 0 ]]; then
break
fi
done
for file in "$path"/*; do
if [ -d "$file" ]; then
recursiveExtract "$file"
elif [ -f "$file" -a "${file##*.}" = 'tar' ]; then
7z x $file -o$path -r # variation 2
rm -f "$file" # comment this if you want to keep the zip files.
recursiveExtract "${file%.tar}"
fi
c=$(ls $path | grep tar | wc -l)
if [[ "$c" -eq 0 ]]; then
break
fi
done
for file in "$path"/*; do
if [ -d "$file" ]; then
recursiveExtract "$file"
elif [ -f "$file" -a "${file##*.}" = 'gz' ]; then
7z x $file -o$path -r # variation 2
rm -f "$file" # comment this if you want to keep the zip files.
recursiveExtract "${file%.gz}"
fi
d=$(ls $path | grep gz | wc -l)
if [[ "$d" -eq 0 ]]; then
break
fi
done
}
recursiveExtract "$1"
Latest version of my code:
#!/bin/bash
# set -x
recursiveExtract () { # $1=directory
path="$(realpath "$1")"
echo "GZ------------------"
for file in "$path"/*; do
if [ -f "$file" -a "${file##*.}" = 'gz' ]; then
echo "File:" $file
echo "Path:" $path
# 7z e "${file%.gz}" -o"$file" # variation 1
7z x $file -o$path -r -aou # variation 2
rm -f "$file" # comment this if you want to keep the zip files.
recursiveExtract "${file%.gz}"
fi
# d=$(ls $path | grep gz | wc -l)
d=$(find $path -type f -name "*.gz" | wc -l)
echo "WC GZ-----------------:" $d
if [[ "$d" -eq 0 ]]; then
break
fi
done
echo "IMG------------------"
for file in "$path"/*; do
if [ -f "$file" -a "${file##*.}" = 'img' ]; then
echo "File:" $file
echo "Path:" $path
# 7z e "${file%.img}" -o"$file" # variation 1
7z x $file -o$path -r -aou # variation 2
rm -f "$file" # comment this if you want to keep the zip files.
recursiveExtract "${file%.img}"
fi
# b=$(ls $path | grep img | wc -l)
b=$(find $path -type f -name "*.img" | wc -l)
echo "WC IMG-----------------:" $b
if [[ "$b" -eq 0 ]]; then
break
fi
done
}
while true
do
d=$(find $1 -type f -name "*.gz" | wc -l)
b=$(find $1 -type f -name "*.img" | wc -l)
if [[ "$d" -eq 0 ]] && [[ "$b" -eq 0 ]]; then
break
else
recursiveExtract "$1"
fi
done
This shoud do the job
#!/bin/bash
find ./ -name "*.zip*" > current_zips.txt
while [[ `wc -l "current_zips.txt" | cut -d' ' -f1` > 0 ]]; do
find ./ -name "*.zip*" -exec unzip {} \;
while IFS= read file_; do rm $file_; done < "current_zips.txt" # for file_ in $(cat current_zips.txt);do rm $file_;done
find ./ -name "*.zip*" > current_zips.txt
done
Use the following script to generate test data
#!/bin/bash
#create some data
echo "data 0" > file0.txt
mkdir folder1 folder2
echo "data 1" > folder1/file1.txt
echo "data 2" > folder2/file1.txt
#zip data
zip file0.zip file0.txt
zip -r folder1.zip folder1
zip -r folder2.zip folder2
zip -r data.zip *.zip
#delete original data
rm -rf file* folder*
I think you want something like this:
recursiveExtract.bash
#!/bin/bash
# efi,gz,img,tar
recursiveExtract () { # $1=file/directory
local path="$(realpath "$1")" # This variable must be local.
for file in "$path"/*; do
if [ -d "$file" ]; then
recursiveExtract "$file"
elif [ -f "$file" -a "${file##*.}" = 'tgz' ]; then # file.tgz == file.tar.gz
local anotherFile="${file%.*}"'.tar'
echo 'Extracting: '"$file"
7z x "$file" -o"${file%/*}" -aoa -bd -bso0
7z x "$anotherFile" -o"${anotherFile%.*}" -aoa -bd -bso0
#######################
# -aoa == Overwrite output files #
# -bd == Don't show progress bar #
# -bso0 == Silent #
#######################
rm -f "$anotherFile" # DO NOT COMMENT THIS LINE.
rm -f "$file" # Comment this if you want to keep the archieves.
recursiveExtract "${anotherFile%.*}"
elif [ -f "$file" -a "${file##*.}" = 'gz' ]; then
local anotherFile="${file%.*}"
if [ -n "$anotherFile" -a \( "${anotherFile##*.}" = 'tar' -o "${anotherFile##*.}" = 'img' \) ]; then # file.tar.gz, file.img.gz
echo 'Extracting: '"$file"
7z x "$file" -o"${file%/*}" -aoa -bd -bso0
7z x "$anotherFile" -o"${anotherFile%.*}" -aoa -bd -bso0
rm -f "$anotherFile" # DO NOT COMMENT THIS LINE.
rm -f "$file" # Comment this if you want to keep the archieves.
recursiveExtract "${anotherFile%.*}"
else # gz
echo 'Extracting: '"$file"
7z x "$file" -o"${file%.*}" -aoa -bd -bso0
rm -f "$file" # Comment this if you want to keep the archieves.
recursiveExtract "{file%.*}"
fi
elif [ -f "$file" -a \( "${file##*.}" = 'img' -o "${file##*.}" = 'tar' \) ]; then # file.img or file.tar
echo 'Extracting: '"$file"
7z x "$file" -o"${file%.*}" -aoa -bd -bso0
rm -f "$file" # Comment this if you want to keep the archieves.
recursiveExtract "${file%.*}"
fi
done
}
recursiveExtract "$1"
Give the script executable permission with chmod +x recursiveExtract.bash then you can run it like:
$ ./recursiveExtract.bash <directory/file>
In your case, maybe like this:
$ ./recursiveExtract.bash 'sample.img.gz'

gzip already has gz suffix unchanged in the script

I have created a script to zip and move log files from one directory to another directory to free space. This is the script:
#!/bin/bash
logsDirectory="/test//logs/"
email=""
backupDirectory="/test/backup"
pid="/data/test/scripts/backup.pid"
usage=$(df | grep /data/logs | awk '{ print $2 }')
space=450000000
getBackup ()
{
if [[ ! -e $pid ]] then
if [[ $usage -le $space ]]
then
touch $pid
find $backupDirectory -mtime +15 -type f -delete;
for i in $(find $logsDirectory -type f -not -path "*/irws/*")
do
/sbin/fuser $i > /dev/null 2>&1
if [ $? -ne 0 ]
then
gzip $i
mv -v $i.gz $backupDirectory
else
continue
fi
done
[[ ! -z $email ]] && echo "Backup is ready" | mas"Backup" $email
rm -f $pid
fi
fi
}
getBackup
I am getting this error:
gzip: /data/logs/log01.log.gz already has .gz suffix -- unchanged
mv: cannot stat `/data/logs/log01.log.gz': No such file or directory
I got the error every time I ran the script in my DEV and PROD (CentOS servers) environments. To analyse it, I ran the same script in a VM (Ubuntu) in my laptop, and I don't get the error there.
My questions:
How can I prevent this error?
What I have done wrong in the script?
Your script contains a number of common clumsy or inefficient antipatterns. Here is a refactoring. The only real change is skipping any *.gz files.
#!/bin/bash
logsDirectory="/test//logs/"
email=""
backupDirectory="/test/backup"
pid="/data/test/scripts/backup.pid"
# Avoid useless use of grep -- awk knows how to match a regex
# Better still run df /data/logs
usage=$(df /data/logs/ | awk '{ print $2 }')
space=450000000
getBackup ()
{
# Quote variables
if [[ ! -e "$pid" ]]; then
if [[ "$usage" -le "$space" ]]; then
touch "$pid"
find "$backupDirectory" -mtime +15 -type f -delete;
# Exclude *.gz files
# This is still not robust against file names with spaces or wildcards in their names
for i in $(find "$logsDirectory" -type f -not -path "*/irws/*" -not -name '*.gz')
do
# Avoid useless use of $?
if /sbin/fuser "$i" > /dev/null 2>&1
then
gzip "$i"
mv -v "$i.gz" "$backupDirectory"
# no need for do-nothing else
fi
done
[[ ! -z "$email" ]] &&
echo "Backup is ready" | mas"Backup" "$email"
rm -f "$pid"
fi
fi
}
getBackup
With a slightly more intrusive refactoring, the proper fix to the find loop would perhaps look something like
find "$logsDirectory" -type f \
-not -path "*/irws/*" -not -name '*.gz' \
-exec sh -c '
for i; do
if /sbin/fuser "$i" > /dev/null 2>&1
then
gzip "$i"
mv -v "$i.gz" "$backupDirectory"
fi
done' _ {} +
where the secret sauce is to have find ... -exec + pass in the arguments to the sh -c script in a way which does not involve exposing the arguments to the current shell at all.
What I have done wrong in the script?
Your script tries to zip every file but the gzip command is rejecting files already zipped
How can I prevent this error?
Have the script check whether the file is zipped or not and only gzip if it corresponds (1). Alternatively, you could force re-compression even if it is already compressed (2).
Going with option number 1):
getBackup ()
{
if [[ ! -e $pid ]] then
if [[ $usage -le $space ]]
then
touch $pid
find $backupDirectory -mtime +15 -type f -delete;
for i in $(find $logsDirectory -type f -not -path "*/irws/*")
do
/sbin/fuser $i > /dev/null 2>&1
if [ $? -ne 0 ]
then
if [[ $i =~ \.gz$ ]]
# File is already zipped
mv -v $i $backupDirectory
else
gzip $i
mv -v $i.gz $backupDirectory
fi
else
continue
fi
done
[[ ! -z $email ]] && echo "Backup is ready" | mas"Backup" $email
rm -f $pid
fi
fi
}

errors running sh script [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I keep getting these errors running my script and i just cannot work it out...
the error that keeps coming up is;
rm: cannot remove ~/my-documents/article:': Is a directory. The directory its referring to is $2...here is my script.
#! /bin/sh
SRC=$1
DES=$2
if [ $# -ne 2 ]; then
echo "1. Please enter the source directory"
echo "2. Please enter the destination directory"
echo "thankyou"
exit
fi
if [ ! -d $1 ]; then
echo "$1 is not a directory please enter a valid directory"
echo "thankyou"
exit
fi
#gives the user a error warning the source directory is invalid
if [ -d $2 ]; then
echo "output directory exists"
else
echo "Output directory does not exist, creating directory"
mkdir $2
fi
#creates the destination directory if one doesn't exist
IFILE=$GETFILES;
FINDFILE=$FINDFILE;
find $1 -name "*.doc" > FINDFILE
find $1 -name "*.pdf" > FINDFILE
find $1 -name "*.PDF" > FINDFILE
#finds doc, pdf & PDF files and sends data to findfile.
while read -r line;
do
cp $line $2
done < FINDFILE
#files read and copied to destination directory
IFILE=$2/$GETFILES;
ls -R $1 | egrep -i ".doc | .pdf" > IFILE;
LCOUNT=0
DIFFCOUNT=0
FOUND=0
ARCHIVE=1
BASE="${line%.*}"
EXTENSION="${line##*.}"
COUNT=$COUNT;
ls $2 | grep ${line%%.*} \; | wc -l
if [[ $COUNT -eq 0 ]];
then
cp $1/$line $2;
else
echo "there is already a file in the output so need to compare"
COMP=$2/$line
fi
while [[ $FOUND -eq 0 ]] && [[ $LCOUNT -lt $COUNT ]];
do
echo "diffcount is $DIFFCOUNT"
###compares the file from the input directory to the file in
###the output directory
if [ $DIFFCOUNT -eq 0 ];
then
echo "file has already been archived no action required"
FOUND=$FOUND [ $FOUND+1 ]
else
LCOUNT=$LCOUNT [ $LCOUNT+1 ]
COMP="OUT"/"$BASE"_"$LCOUNT"."$EXTENSION"
echo "line count for next compare is $LCOUNT"
echo "get the next file to compare"
echo "the comparison file is now $COMP"
fi
if [ $LCOUNT -ne $COUNT ]; then
ARCHIVE=$ [ $ARCHIVE+1 ]
else
ARCHIVE=0
fi
if [ $ARCHIVE -eq 0 ];
then
NEWOUT="OUT"/"$BASE"_"$LCOUNT"."$EXTENSION";
echo "newfile name is $NEWOUT"
cp $1/$LINE $NEWOUT
fi
done < $IFILE
rm $IFILE
OFILE=$2/DOCFILES;
ls $2 | grep ".doc" > $OFILE;
while read -r line;
do
BASE=${line%.*}
EXTENSION=${line##*.}
NEWEXTENSION=".pdf"
SEARCHFILE=$BASE$NEWEXTENSION
find $2 -name "$SEARCHFILE" -exec {} \;
done < $OFILE
rm $OFILE
### this will then remove any duplicate files so only
### individual .doc .pdf files will exist
a plain call to rm can only remove files, not directories.
$ touch /tmp/myfile
$ rm /tmp/myfile
$ mkdir /tmp/mydir
$ rm /tmp/mydir
rm: cannot remove ‘/tmp/mydir/’: Is a directory
You can remove directories by specifying the -d (to delete empty directories) or the -r (to delete directories and content recursively) flag:
$ mkdir /tmp/mydir
$ rm -r /tmp/mydir
$
this is well described in man rm.
apart from that, you seem to ignore quoting:
$ rm $OFILE
might break badly if the value of OFILE contains spaces, use quotes instead:
$ rm "${OFILE}"
and never parse the output of ls:
ls $2 | grep ".doc" > $OFILE
(e.g. if your "$2" is actually "/home/foo/my.doc.files/" it will put all files in this directory into $OFILE).
and then you iterate over the contents of this file?
instead, just use loop with file-globbing:
for o in "${2}"/*.doc
do
## loop code in here
done
or just do the filtering with find (and don't forget to call an executable with -exex):
find "$2" -name "$SEARCHFILE" -mindepth 1 -maxdepth 1 -type f -exec convertfile \{\} \;

Archive old files only AND re-construct folder tree in archive

I want to move all my files older than 1000 days, which are distributed over various subfolders, from /home/user/documents into /home/user/archive. The command I tried was
find /home/user/documents -type f -mtime +1000 -exec rsync -a --progress --remove-source-files {} /home/user/archive \;
The problem is, that (understandably) all files end up being moved into the single folder /home/user/archive. However, what I want is to re-construct the file tree below /home/user/documents inside /home/user/archive. I figure this should be possible by simply replacing a string with another somehow, but how? What is the command that serves this purpose?
Thank you!
I would take this route instead of rsync:
Change directories so we can deal with relative path names instead of absolute ones:
cd /home/user/documents
Run your find command and feed the output to cpio, requesting it to make hard-links (-l) to the files, creating the leading directories (-d) and preserve attributes (-m). The -print0 and -0 options use nulls as record terminators to correctly handle file names with whitespace in them. The -l option to cpio uses links instead of actually copying the files, so very little additional space is used (just what is needed for the new directories).
find . -type f -mtime +1000 -print0 | cpio -dumpl0 /home/user/archives
Re-run your find command and feed the output to xargs rm to remove the originals:
find . -type f -mtime +1000 -print0 | xargs -0 rm
Here's a script too.
#!/bin/bash
[ -n "$BASH_VERSION" ] && [[ BASH_VERSINFO -ge 4 ]] || {
echo "You need Bash version 4.0 to run this script."
exit 1
}
# SOURCE=/home/user/documents/
# DEST=/home/user/archive/
SOURCE=$1
DEST=$2
declare -i DAYSOLD=10
declare -a DIRS=()
declare -A DIRS_HASH=()
declare -a FILES=()
declare -i E=0
# Check directories.
[[ -n $SOURCE && -d $SOURCE && -n $DEST && -d $DEST ]] || {
echo "Source or destination directory may be invalid."
exit 1
}
# Format source and dest variables properly:
SOURCE=${SOURCE%/}
DEST=${DEST%/}
SOURCE_LENGTH=${#SOURCE}
# Copy directories first.
echo "Creating directories."
while read -r FILE; do
DIR=${FILE%/*}
if [[ -z ${DIRS_HASH[$DIR]} ]]; then
PARTIAL=${DIR:SOURCE_LENGTH}
if [[ -n $PARTIAL ]]; then
TARGET=${DEST}${PARTIAL}
echo "'$TARGET'"
mkdir -p "$TARGET" || (( E += $? ))
chmod --reference="$DIR" "$TARGET" || (( E += $? ))
chown --reference="$DIR" "$TARGET" || (( E += $? ))
touch --reference="$DIR" "$TARGET" || (( E += $? ))
DIRS+=("$DIR")
fi
DIRS_HASH[$DIR]=.
fi
done < <(exec find "$SOURCE" -mindepth 1 -type f -mtime +"$DAYSOLD")
# Copy files.
echo "Copying files."
while read -r FILE; do
PARTIAL=${FILE:SOURCE_LENGTH}
cp -av "$FILE" "${DEST}${PARTIAL}" || (( E += $? ))
FILES+=("$FILE")
done < <(exec find "$SOURCE" -mindepth 1 -type f -mtime +"$DAYSOLD")
# Remove old files.
if [[ E -eq 0 ]]; then
echo "Removing old files."
rm -fr "${DIRS[#]}" "${FILES[#]}"
else
echo "An error occurred during copy. Not removing old files."
exit 1
fi

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