2013-02-20 119 views
0

批量重命名文件和文件夹是一个经常被问到的问题,但经过一番搜索之后,我认为没有一个类似于我的。使用“索引”重命名批量(基本名称)文件/文件夹

背景:我们派一些生物样品返回具有独特名称的文件和文本格式包含表服务供应商,其中包括信息,文件名和源自它的样本:

head samples.txt 
fq_file Sample_ID Sample_name Library_ID FC_Number Track_Lanes_Pos 
L2369_Track-3885_R1.fastq.gz S1746_B_7_t B 7 t L2369_B_7_t 163 6 
L2349_Track-3865_R1.fastq.gz S1726_A_3_t A 3 t L2349_A_3_t 163 5 
L2354_Track-3870_R1.fastq.gz S1731_A_GFP_c A GFP c L2354_A_GFP_c 163 5 
L2377_Track-3893_R1.fastq.gz S1754_B_7_c B 7 c L2377_B_7_c 163 7 
L2362_Track-3878_R1.fastq.gz S1739_B_GFP_t B GFP t L2362_B_GFP_t 163 6 

目录结构(34个目录):

L2369_Track-3885_ 
    accepted_hits.bam  
    deletions.bed 
    junctions.bed   
    logs 
    accepted_hits.bam.bai 
    insertions.bed 
    left_kept_reads.info 
L2349_Track-3865_ 
    accepted_hits.bam  
    deletions.bed 
    junctions.bed   
    logs 
    accepted_hits.bam.bai 
    insertions.bed 
    left_kept_reads.info 

目标:因为文件名是毫无意义的,很难解释,我要重命名.bam结束(保持后缀)的文件和文件夹与通信样品名称,以更合适的方式重新排序。结果应该是这样的:

7_t_B 
    7_t_B..bam  
    deletions.bed 
    junctions.bed   
    logs 
    7_t_B.bam.bai 
    insertions.bed 
    left_kept_reads.info 
3_t_A 
    3_t_A.bam  
    deletions.bed 
    junctions.bed   
    logs 
    accepted_hits.bam.bai 
    insertions.bed 
    left_kept_reads.info 

我砍死在一起使用bash和python(新手)的解决方案,但感觉过度设计。问题是,是否有更简单/更优雅的方式来实现这一点,我错过了?解决方案可以使用python,bash和R.也可以awk,因为我正在尝试学习它。作为一个相对的初学者确实会让事情变得复杂。

这是我的解决方案:

的包装纸把它全部到位,并给出了工作流程的一个想法:

#! /bin/bash 

# select columns of interest and write them to a file - basenames 
tail -n +2 samples.txt | cut -d$'\t' -f1,3 >> BAMfilames.txt 

# call my little python script that creates a new .sh with the renaming commmands 
./renameBamFiles.py 

# finally do the renaming 
./renameBam.sh 

# and the folders to 
./renameBamFolder.sh 

renameBamFiles.py:

#! /usr/bin/env python 
import re 

# Read in the data sample file and create a bash file that will remane the tophat output 
# the reanaming will be as follows: 
# mv L2377_Track-3893_R1_ L2377_Track-3893_R1_SRSF7_cyto_B 
# 

# Set the input file name 
# (The program must be run from within the directory 
# that contains this data file) 
InFileName = 'BAMfilames.txt' 


### Rename BAM files 

# Open the input file for reading 
InFile = open(InFileName, 'r') 


# Open the output file for writing 
OutFileName= 'renameBam.sh' 

OutFile=open(OutFileName,'a') # You can append instead with 'a' 

OutFile.write("#! /bin/bash"+"\n") 
OutFile.write(" "+"\n") 


# Loop through each line in the file 
for Line in InFile: 
    ## Remove the line ending characters 
    Line=Line.strip('\n') 

    ## Separate the line into a list of its tab-delimited components 
    ElementList=Line.split('\t') 

    # separate the folder string from the experimental name 
    fileroot=ElementList[1] 
    fileroot=fileroot.split() 

    # create variable names using regex 
    folderName=re.sub(r'^(.*)(\_)(\w+).*', r'\1\2\3\2', ElementList[0]) 
    folderName=folderName.strip('\n') 
    fileName = "%s_%s_%s" % (fileroot[1], fileroot[2], fileroot[0]) 

    command= "for file in %s/accepted_hits.*; do mv $file ${file/accepted_hits/%s}; done" % (folderName, fileName) 

    print command 
    OutFile.write(command+"\n") 


# After the loop is completed, close the files 
InFile.close() 
OutFile.close() 


### Rename folders 

# Open the input file for reading 
InFile = open(InFileName, 'r') 


# Open the output file for writing 
OutFileName= 'renameBamFolder.sh' 

OutFile=open(OutFileName,'w') 

OutFile.write("#! /bin/bash"+"\n") 
OutFile.write(" "+"\n") 


# Loop through each line in the file 
for Line in InFile: 
    ## Remove the line ending characters 
    Line=Line.strip('\n') 

    ## Separate the line into a list of its tab-delimited components 
    ElementList=Line.split('\t') 

    # separate the folder string from the experimental name 
    fileroot=ElementList[1] 
    fileroot=fileroot.split() 

    # create variable names using regex 
    folderName=re.sub(r'^(.*)(\_)(\w+).*', r'\1\2\3\2', ElementList[0]) 
    folderName=folderName.strip('\n') 
    fileName = "%s_%s_%s" % (fileroot[1], fileroot[2], fileroot[0]) 

    command= "mv %s %s" % (folderName, fileName) 

    print command 

    OutFile.write(command+"\n") 


# After the loop is completed, close the files 
InFile.close() 
OutFile.close() 

RenameBam.sh - 由以前的python脚本创建:

#! /bin/bash 

for file in L2369_Track-3885_R1_/accepted_hits.*; do mv $file ${file/accepted_hits/7_t_B}; done 
for file in L2349_Track-3865_R1_/accepted_hits.*; do mv $file ${file/accepted_hits/3_t_A}; done 
for file in L2354_Track-3870_R1_/accepted_hits.*; do mv $file ${file/accepted_hits/GFP_c_A}; done 
(..) 

重命名renameBamFolder.sh非常相似:

mv L2369_Track-3885_R1_ 7_t_B 
mv L2349_Track-3865_R1_ 3_t_A 
mv L2354_Track-3870_R1_ GFP_c_A 
mv L2377_Track-3893_R1_ 7_c_B 

自从我学习,我觉得的这样做,并思考如何做到这一点的不同方法的一些示例,将是非常有用的。

+2

使用Python生成bash似乎有点没有意义。我会说选择一种语言或其他语言,然后使用它。如果你不习惯,Python也许不那么神秘。 – 2013-02-20 13:11:30

回答

2

一个简单的方法:

find . -type d -print | 
while IFS= read -r oldPath; do 

    parent=$(dirname "$oldPath") 
    old=$(basename "$oldPath") 
    new=$(awk -v old="$old" '$1~"^"old{print $4"_"$5"_"$3}' samples.txt) 

    if [ -n "$new" ]; then 
     newPath="${parent}/${new}" 
     echo mv "$oldPath" "$newPath" 
     echo mv "${newPath}/accepted_hits.bam" "${newPath}/${new}.bam" 
    fi 
done 

初步测试后删除 “回声” S得到它实际做的 “MV” S。

如果所有的目标目录都在@ triplee的答案所暗示的一个级别,那么它就更简单了。只是cd到它们的父目录,并做:

awk 'NR>1{sub(/[^_]+$/,"",$1); print $1" "$4"_"$5"_"$3}' samples.txt | 
while read -r old new; do 
    echo mv "$old" "$new" 
    echo mv "${new}/accepted_hits.bam" "${new}/${new}.bam" 
done 

在您的预期产出之一,你改名为“.bai”文件,在对方你没有,如果你想这样做,你不说或不。如果你想重新命名,只需添加

echo mv "${new}/accepted_hits.bam.bai" "${new}/${new}.bam.bai" 

以上任何你喜欢的解决方案。

+0

awk解决方案是迄今为止最优雅的imo,即使我从未学过awk,但我直观地设法根据您的解决方案更改脚本以重命名另一个类似的一组文件。您可能会考虑在您的解决方案中改变@EdMorton的唯一方法是字段顺序:打印$ 1“”$ 4“_”$ 5“_”$ 3应该打印$ 1“”$ 3“_”$ 4“_”$ 2。非常感谢。 – fridaymeetssunday 2013-02-22 14:46:26

0

当然,你只能在Python中完成 - 它可以产生一个小的可读脚本。

第一件事:阅读sampels.txt文件并创建一个从现有文件前缀到所需映射前缀的映射 - 该文件未格式化为使用Python CSV阅读器模块,因为在最后的数据中使用了列分隔符柱。

mapping = {} 
with open("samples.txt") as samples: 
    # throw away headers 
    samples.readline() 
    for line in samples(): 
     # separate the columns spliting the first whitespace ocurrences: 
     # (either space sequences or tabs) 
     fields = line.split() 
     # skipp blank, malformed lines: 
     if len(fields) < 6: 
      continue 
     fq_file, sample_id, Sample_name, Library_ID, FC_Number, track_lanes_pos, *other = fields 
     # the [:-2] part is to trhow awauy the "R1" sufix as for the example above 
     file_prefix = fq_file.split(".")[0][:-2] 
     target_id = "_".join((Library_ID, FC_number. Sample_name)) 
     mapping[file_prefix] = target_id 

然后检查dir名称,并在每个名称中添加“.bam”文件以进行重新映射。在bash

import os 
for entry in os.listdir("."): 
    if entry in mapping: 
     dir_prefix = "./" + entry + "/") 
     for file_entry in os.listdir(dir_prefix): 
       if ".bam" in file_entry: 
        parts = file_entry.split(".bam") 
        parts[0] = mapping[entry] 
        new_name = ".bam".join(parts) 

        os.rename(dir_prefix + file_entry, dir_prefix + new_name) 
     os.rename(entry, mapping[entry]) 
0

似乎只需从简单的while循环中的索引文件中读取必需的字段即可。文件的结构并不明显,所以我假定文件是空格分隔的,并且Sample_Id实际上是四个字段(复杂的sample_id,然后是名称中的三个组件)。也许你在Sample_Id字段中有一个带有内部空格的制表符分隔的文件?无论如何,如果我的假设是错误的,这应该很容易适应。

# Skip the annoying field names 
tail +1 samples.txt | 
while read fq _ c a b chaff; do 
    dir=${fq%R1.fastq.gz} 
    new="${a}_${b}_$c" 
    echo mv "$dir"/accepted_hits.bam "$dir/$new".bam 
    echo mv "$dir"/accepted_hits.bam.bai "$dir/$new".bam.bai 
    echo mv "$dir" "$new" 
done 

取出echo■如果输出看起来像你想要什么。

+0

该文件是制表符分隔的,有些字段中有空格。这就是为什么OP在脚本中使用'cut -d $'\ t''的原因。如果您在问题上点击“编辑”,您将看到选项卡。 – dogbane 2013-02-20 14:13:57

+0

我很抱歉@tripleee,这些字段是制表符分隔的,我使用字段Sample_name,但它不一定是那个特定的字段。 – fridaymeetssunday 2013-02-20 14:17:14

0

这是使用shell脚本的一种方法。运行像:的script.sh

script.sh /path/to/samples.txt /path/to/data 

内容:

# add directory names to an array 
while IFS= read -r -d '' dir; do 

    dirs+=("$dir") 

done < <(find $2/* -type d -print0) 


# process the sample list 
while IFS=$'\t' read -r -a list; do 

    for i in "${dirs[@]}"; do 

     # if the directory is in the sample list 
     if [ "${i##*/}" == "${list[0]%R1.fastq.gz}" ]; then 

      tag="${list[3]}_${list[4]}_${list[2]}" 
      new="${i%/*}/$tag" 
      bam="$new/accepted_hits.bam" 

      # only change name if there's a bam file 
      if [ -n $bam ]; then 

       mv "$i" "$new" 
       mv "$bam" "$new/$tag.bam" 
      fi 
     fi 
    done 

done < <(tail -n +2 $1) 
0

虽然这是你要找不正是对(只是想禁区外):你可能会考虑你的文件的替代“视图”系统 - 使用像数据库视图这样的术语“视图”就是表格。您可以通过FUSE中的“用户空间中的文件系统”来完成此操作。人们可以用许多现有的工具来做到这一点,但我不知道只是一般地处理任何一组文件,专门用于重命名/重新组织。但作为如何使用它的具体示例,pytagsfs根据您定义的规则创建virtual (fuse) file system,使您可以显示文件的目录结构。 (也许这也适用于你 - 但pytagsfs实际上是用于媒体文件的。)然后,你只需使用任何通常访问该数据的程序在该(虚拟)文件系统上进行操作。或者,为了使虚拟目录结构永久化(如果pytagsfs没有选项可以执行此操作),只需将虚拟文件系统复制到另一个目录(虚拟文件系统之外)。

相关问题