1

I have 23 different directories and in each one of them I have a file called accepted_hits.bam

so I need to generate a unique name in each directory: e.g accepted_hits.bam_1 for directory 1 accepted_hits.bam_2 for directory 2

and so on 23 times. Is there a easy way to do that with a single command?

0

5 Answers 5

2

With bash and find

find . -type f -name accepted_hits.bam -exec bash -c \
'i=0; for f; do (( ++i )); mv -- "$f" "${f}_$i"; done' _ {} +
1
  • this renames all the files as accepted_hits.bam_1. I was also going to post a similar one but then noticed the counter does not get incremented for each file.
    – rahul
    May 15, 2015 at 19:57
1

Assuming your directories are all at the same parent, try this:

$ for f in $(ls */accepted_hits.bam); do mv $f $(dirname $f)/$(dirname $f)-$(basename $f); done
0

I would probably use awk, but that's just a matter of taste, not of right and wrong:

find . -name accepted_hits.bam | awk '{ print "cp -pi \"" $0 "\" \"" $0 "\"_" NR "; mv -i \"" $0 "\"_" NR " ./bam-files/" }' | tcsh -f

where you need to create a directory ./bam-files/ before calling above line, or adjust the directory name. I have intentionally used -p for preserving file times and -i for avoiding overwrites, just in case. So, before calling the commands for a 2nd time please clean the target directory.

If you wish to remove the original files please replace the cp -pi by mv -i.

The script leaves out calls to basename in the hope to run into less trouble if the pathname contains special characters (spaces and most special characters are handled).

PS: You could also do a dry-run before executing by leaving out the | tcsh -cf at the end. If you do not have tcsh installed (yes, there are Linuxes without it) feel free to use sh or bash.

0

If you would just like to name them as unique files, you can use the RANDOM variable as below:

find /basedir/ -type f -name "accepted_hits.bam" -exec bash -c 'mv $0 $0.$RANDOM' {} +

If you would like to sequentially name them, you can append a counter value at the end of the filename as below:

for file in $(find /basedir/ -type f -name "accepted_hits.bam"); do ((count++)) ; mv $file ${file}_${count}; done

The above two methods rename the file in the same directory. If you'd like to rename them to a different directory, you can combine them with @schlimmchen's answer ( using basename and dirname ). You can even insert an echo statement before the move command to see the result without actually moving the files. I've found it a handy way to test the commands without risking any changes to the files.

0

This is very easy do to with shell globs:

  1. Add a counter to the name (accepted_hits.bam.1, accepted_hits.bam.2 and so on):

    c=0; for f in */accepted_hits.bam; do mv "$f" "$f".$((++c)); done
    

    Explanation

    This simply iterates over all files called accepted_hits.bam that are in subdirectories of the current directory, and saves each of them as $f. The $((++c)) construct increments a counter ($c), so $f.$((++c)) will be file.1, file.2 etc, incremented on each iteration.

    Note that I am using c=0 at the beginning. If you don't do this, subsequent runs will keep the value of $c and it will keep incrementing.

  2. Add a counter but before the extension (accepted_hits.1.bam, accepted_hits.2.bam and so on):

    c=0; for f in */accepted_hits.bam; do  
        mv "$f" "$(dirname "$f")"/"$(basename "$f" .bam)".$((++c)).bam
    done
    

    Explanation

    The command dirname returns the path to a file's parent directory:

    $ dirname /usr/local/bin/apt 
    /usr/local/bin
    

    The command basename is the inverse, it removes the directory, leaving only the last part of the path and can also remove an extension:

    $ basename /etc/apt/sources.list
    sources.list
    $ basename /etc/apt/sources.list .list   ## remove extension
    sources
    

    So, mv "$f" "$(dirname "$f")"/"$(basename "$f" .bam)".$((++c)).bam will rename the file as desired.

  3. Add the parent directory's name (accepted_hits.dir1.bam, accepted_hits.dir2.bam, or whatever your directory names are)

    for f in */accepted_hits.bam; do  
        dir="$(dirname "$f")"; mv "$f" "$dir"/"$(basename "$f" .bam)"."$dir".bam
    done
    

    Explanation

    This is the same idea as above, only I am saving the directory's name as a variable to simplify using it as part of the file name.

  4. Use external tools. Perl rename for example. The standard rename that ships with Debian-based distributions can't deal with incrementing counters. However, I found this one which can. Download the script linked to in that page, save it as increname in your current directory and run it like this:

    perl increname -n 's/.bam/++$c . ".bam"/e' */accepted_hits.bam 
    
2
  • @don_crissti the OP hasn't said that the directories are numbered, so I don't see any other way to do it. I don't see how you can be so sure as to say that's definitely not what the OP wants. How do you know that the directory names contain numbers? In fact, since these are .bam files, the directories are probably names after chromosomes and include chrx and chry.
    – terdon
    May 16, 2015 at 17:52
  • @don_crissti actually, since these are .bam files (a common format in bioinformatics) the directories are probably names chr1 through 22 and chry. Hence my solution using the directory name. Can't know unless the OP clarifies though.
    – terdon
    May 16, 2015 at 18:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .