Shortest way to swap two files in bash

LinuxBashCommand Line

Linux Problem Overview


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

Linux Solutions


Solution 1 - Linux

$ 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"
}

Solution 2 - Linux

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.

Solution 3 - Linux

tmpfile=$(mktemp $(dirname "$file1")/XXXXXX)
mv "$file1" "$tmpfile"
mv "$file2" "$file1"
mv "$tmpfile" "$file2"

Solution 4 - Linux

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

Solution 5 - Linux

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"
}

Solution 6 - Linux

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/

Solution 7 - Linux

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"

Solution 8 - Linux

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.

Solution 9 - Linux

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 ;-)

Solution 10 - Linux

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.

Solution 11 - Linux

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.

Solution 12 - Linux

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

Solution 13 - Linux

Surely mv instead of cp?

Solution 14 - Linux

mv old tmp
mv curr old
mv tmp curr

Solution 15 - Linux

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionflybywireView Question on Stackoverflow
Solution 1 - LinuxCan BalView Answer on Stackoverflow
Solution 2 - LinuxHardyView Answer on Stackoverflow
Solution 3 - LinuxcubeView Answer on Stackoverflow
Solution 4 - LinuxŁukasz RysiakView Answer on Stackoverflow
Solution 5 - LinuxLeslie ViljoenView Answer on Stackoverflow
Solution 6 - LinuxYvoView Answer on Stackoverflow
Solution 7 - LinuxRichardView Answer on Stackoverflow
Solution 8 - LinuxcayhorstmannView Answer on Stackoverflow
Solution 9 - LinuxtsoomoView Answer on Stackoverflow
Solution 10 - LinuxdjnaView Answer on Stackoverflow
Solution 11 - Linuxadam_0View Answer on Stackoverflow
Solution 12 - Linuxideasman42View Answer on Stackoverflow
Solution 13 - LinuxAlastairView Answer on Stackoverflow
Solution 14 - LinuxarturhView Answer on Stackoverflow
Solution 15 - LinuxBonaparteView Answer on Stackoverflow