Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

How can I generate a new name for a file if there's an existing file With that same name? In a Desktop environment a new name is generated my appending a number to the end of the file name, but how can this be accomplished from the command line?

I'm using the Android operating system with Busybox.

share|improve this question
1  
Can you expand you Q to specify in what capacity you want to make these files with respect to android? Java? What tooling do you have, busybox? Or just vanilla android? –  slm Sep 6 '13 at 20:22
    
@user slm I am going to use it for backing up the files contained in the download folder on my tablet, the program sorts the files based on the extension into there respective folders, i have written a python script that does this, but the program is slow and also overwrites a file if it has the same name. I am in the process of rewriting the program into bash, generating a new name is the part that i was struggling with. –  kyle k Sep 6 '13 at 20:34
    
Thank you for you answer! Do you have access then to a version of mktemp? –  slm Sep 6 '13 at 20:36
    
@user slm I have busybox installed and it is included, but if i do a system call from a program mktemp will not work. –  kyle k Sep 6 '13 at 20:43
    
Doesn't work with some kind of error? Also where are you making this system call from? Python? –  slm Sep 6 '13 at 20:44

3 Answers 3

up vote 5 down vote accepted

Assuming you have a POSIX shell, you can do this:

mv() {
        eval "DEST=\${$#}" #The destination is the last positional parameter
        if [ -e "$DEST" ] && ! [ -d "$DEST" ];then
                PREFIX=${DEST%.*}
                COUNT=1
                EXT=${DEST##*.}
                args= i=1
                while [ $i -lt $# ]; do args="$args \"\${$i}\"" i=$((i+1)); done
                DEST="$NAME-"$(printf "%03d" $COUNT)".$EXT"
                while [ -e "$DEST" ];do
                    COUNT=$((COUNT+1))
                    DEST="$NAME-"$(printf "%03d" $COUNT)".$EXT"
                done
                eval "command mv $args \"\$DEST\""
        else
                command mv "$@"
        fi
}

How to use this

This is a function so save it in your ~/.bashrc and call it as you would the normal mv.

What this does

  • Stores the path to the original mv executable in the MV variable
  • Gets the last argument it was called with into the variable DEST
  • If DEST exists and is not a directory, this function assumes that your rename is trying to clobber a file
  • It then extracts the final name's prefix (anything before the final ., which marks the extension), the extension (anything after the final .), the count (if any; anything in the prefix after the final -).
  • The extracted count is set to zero if no count was found otherwise, it's set to the count found in the previous step.
  • The current count is incremented
  • The function then calls itself with all the original arguments (switches + file names) minus the last one and adds the new file name instead of the last argument in the original call. The new name is the old name but with a 3 digit counter (with zero-stuffing) added before the extension.
  • The function is recursive because if you were saying mv a.txt b.txt it will first try mv a.txt b-001.txt. This next mv call must also be the function itself because if b-001.txt also exists, we want to keep incrementing the counter until we find a new file name that doesn't exist.
  • If the final argument doesn't exist or is a directory, the original mv executable is called with your original arguments.

Caveats

  • The number of times you can repeatedly try to clobber an existing file depends on the length of the counter (999 times in this case). You may choose a number of digits that encompasses the inode limit on your filesystem to ensure that this will work for as long as you're able to create files.
  • If you try to clobber a file whose name is similar to foo-001.txt, it will be moved to foo-001-001.txt.

Notes

  • To change the naming pattern, change the 3 in the printf statement to whatever you like.
  • This code has been tested
  • This is very simplistic and I'm sure there will be edge cases where it fails miserably. I'm happy to try and fix them for you if you find any. In the meantime, don't try this on a production machine.
share|improve this answer
    
same mind here :P –  Rahul Patil Sep 6 '13 at 19:54
    
@RahulPatil Except I'm attempting to duplicate the GUI behavior. –  Joseph R. Sep 6 '13 at 19:56
    
that's great, I haven't used GUI yet. –  Rahul Patil Sep 6 '13 at 19:57
    
it's overwriting the destination file if it exits , I have tested paste.ubuntu.com/6071897 –  Rahul Patil Sep 6 '13 at 20:04
    
@RahulPatil Not sure what you're saying: in your example, it identified that /tmp is an existent directory and called /bin/mv file1 /tmp I don't see where the problem is. –  Joseph R. Sep 6 '13 at 20:06

I generally use the tool mktemp to create reliable temporary files. It defaults to creating files, but can also make directories too, through its -d switch.

Example

Here's how you can create some temporary names to files in your current directory.

$ mktemp somefile.XXXXX
somefile.kiDad

$ mktemp somefile.XXXX
somefile.MrSW

$ mktemp someotherfile.XXXXXXXXXXX
someotherfile.Um4aXKrt3lv

This will create the files for you.

References

share|improve this answer
2  
No mktemp on android, but +1 from me anyway since this is a good answer to a good question, U&L wise. –  goldilocks Sep 6 '13 at 20:10
    
@goldilocks - thank you for your kind words. It's always nice! –  slm Sep 6 '13 at 20:11
    
@goldilocks - do you know offhand if busybox is included? There's an implementation of mktemp in that, I do not know that much about android. code.google.com/p/abb/source/browse/mktemp.c?name=upstream/… –  slm Sep 6 '13 at 20:18
1  
Looks like busybox on android requires the device be rooted (jailbroken). The native shell is /system/bin/sh and seems to be sh compatible -- constructs like if [ -w $file ] work, mv and rename are available -- so a script to do this just with built-ins should work there. –  goldilocks Sep 6 '13 at 20:52
    
@goldilocks - thanks for checking. What are you looking in to find this out? I'd like to get my feet wet with android but don't want to have a phone/device. –  slm Sep 6 '13 at 21:03

Here's an alternative to Joseph R's script which has none of the caveats! It will append a numerical suffix to a path name (the path can be a directory or a file), incrementing the suffix value until one is found which does not already exist. Other utilities such as logrotate use a similar pattern, but rotate all existing copies so that the new one always has '0' for a suffix. Since this isn't a rotation in that sense, I'll call it dotmv. Just remember that file.0 will be the oldest copy.

For example:

dotmv somefile.txt

Renames somefile.txt somefile.txt.0, unless the latter exists, in which case it will be somefile.txt.1, and so on. You can list more than one file (dotmv this that "the other thing" etc.), all of them will be dot-moved.

I believe this is POSIX compliant -- it runs with set -o posix on bash (but that is a dubious test). I also tested with the android (jelly bean 4.2.1) shell, and it works there. However, on android you will need to change the shebang as indicated or run it sh dotmv -- which you will anyway unless you have a rooted device, because there is no way to make a script executable otherwise. Changing the shebang will allow you to use exec dotmv.

#!/bin/sh
# On android change that to /system/bin/sh.

# Validate arguments
if [ $# -lt 1 ]; then
    echo "A list of one or more paths is required."
    exit 1
fi

# Checks if a path exists and can be moved.
checkPath () {
    if [ ! -e "$1" ]; then
        echo "'$1' does not exist."
        return 1;
    fi
    if [ ! -w "$1" ]; then
        echo "Cannot move '$1', permission denied."
        return 1;
    fi
    return 0;
}

# Finds a new path with numerical suffix.
getName () {
    suf=0;
    while [ -e "$1.$suf" ]
        do let suf+=1
    done
    Dest=$1.$suf
}

# Loop through arguments -- use quotes to allow spaces in paths.
while (($#)); do
    Src=$1
    Dest=$1
    shift
    checkPath "$Src"
    if [ $? -eq 0 ]; then
        getName "$Src"
        mv "$Src" "$Dest"
    fi
done

Hopefully the logic here is very straight-forward. This could be implemented in python, C, or any other turing complete procedural language with file I/O.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.