批量文件操作脚本

批量文件操作脚本

2012930

 

在日常的工作与生活中,有时需要批量地操作一组文件或目录。例如删除后缀为.temp类型的文件、更改错乱的文件名、提取某个目录下的图片。所以,为了方便,这里使用Python脚本统一常见的批量操作,在不同的场景下,传入不同的命令行参数完成不同的功能。

下载地址:http://aspirationflowspace.googlecode.com/files/flow.py

1  Python脚本

此处我们编写Python脚本来批量处理更改文件名、删除文件、提取文件等操作。

脚本名字为flow.py

基本格式如下:

flow.py[options] operate filename1/2 --dir directory

 

options表示选项,目前支持三个选项:

--re,表示regularexpression,被匹配的文件名字以正则表达式方式指定;

--cd,表示changedirectory,该操作会影响到目录;没有该选项时,所有的操作只针对普通文件。

--onlydir,表示被操作的对象只有目录,不操作普通文件。

 

operate字段,表示操作类型,目前支持三种操作类型:

--changename oldname newname:将与oldname匹配的文件或目录的名字修改为newname

--delete filename:将与filename匹配的文件或者目录删除掉。

--extract filename:将与filename匹配的文件提取到exdir指定的目录下面,若该目录不存在,则新创建目录。提取的方式以copy方式实现,不影响原有的目录结构。提取的结果保持原来的层次结构,会新创建对应的目录树。

 

目录相关选项

目录指定可以是绝对路径(如C:/MyFiles/)或相对路径(如./test/)。注意,如果输入的路径包含空格,需要使用引号,如”C:/test app/project”

--dir directory:用于指定被操作文件所在的根目录,所有的操作都发生在该目录下。

--extractdir extdirectory:仅用于提取操作(extract)时候,指定存放提取文件的目的目录,若该目录不存在,将新建目录。

名字和目录格式说明

文件名格式支持普通文件名、通配符(wildcard)描述、正则表达式(regular expression)描述。默认情况下,文件名被视为普通文件名或通配符。如test.temp表示匹配名字叫test.temp所有的文件,而*.txt表示,匹配扩展名为txt的所有文件。而当使用选项--re时,可以用正则表达式指定被操作的文件,如bk.*[0-9]+\.cpp,表示匹配以bk开头,以数字串结尾的所有cpp文件。

脚本默认情况只操作普通文件。若想一并修改目录,可以使用--cd命令行选项。若只想操作目录,则使用--onlydir选项。

 

1.1修改文件名

用于批量修改目录下的文件名字或子目录的名字。

基本格式:

flow.py[options] --changename f1 f2 --dir directoty

操作为--changenameoptions部分可以是上面介绍的各个选项,也可以省略。f1为原来的文件名,f2为更改后的文件名。--dir指定被操作的目录。

更改后的名字f2,可以包含特殊字符指定格式:

%D:表示当前日期(如20121013

%T:表示当前时间(如165819

%C:表示按照序号递增方式命名。

特殊格式可用于方便地根据日期、时间、文件个数来命名文件。

若在更改文件名字过程中,发生重名,则自动添加序号区分。


示例:

flow.py--changename tmp.txt tmp_bk.txt --dir F:/project

F:/project目录下所有名字为tmp.txt文件更改为tmp_bk.txt,会递归搜索所有的子目录。

 

flow.py--changename *.conf %D.conf%C.bk--dir ./

将当前目录(由选项--dir ./指定)下所有的后缀为conf的文件更改为“日期.conf计数值.bk”,其中日期为当前日期,计数值为总共更改的统计个数。

 

flow.py--re --changename (.*)\.java \1.jbk --dir ./test

./test目录下所有的java文件的扩展名更改为jbk,如test.java改为test.jbk。注意此处使用了正则表达式描述文件的匹配与改后的名字,其中(.*)\.java表示后缀为java的文件(与通配符*.java含义相同,(.*)是子表达式),而\1表示之前括号内匹配的子表达式,此处也就是基本文件名。

简单的演示上述更改java名字的过程:

更改前的目录内容:


执行Python脚本:


更改后的文件夹的内容:


此处将java修改jbk文件,其中jbk是用户随意定义的后缀(在此处表示java backup),将java文件修改为备份文件。

 

1.2删除文件

若想批量地删除指定目录下的文件或子目录,可以使用delete操作。

同样文件名可以使用通配符或正则表达式指定。

【提醒】删除的文件不会被放入到回收站,所以一旦删除,无法恢复。

为了安全起见,最好将要进行删除操作的目录备份一下,防止操作不当造成的数据损失。

 

示例:

flow.py--delete test.txt --dir F:/testdir/

将目录F:/testdir/下的所有名字为test.txt文件的删除。

 

flow.py--delete *.cpp --dir F:/testcpp/

将目录F:/testcpp/下的所有的cpp文件删除,此处使用通配符指定文件名。

 

flow.py--re --cd --delete .*test.* --dir F:/testdir/

将目录F:/testdir下的所有名字中包含test的文件和目录都删除,此处使用正则表达式描述被删除的文件名,其中.*表示任意长度的任意字符。

 

flow.py--onlydir --delete .svn --dir F:/project/test/

将目录F:/project/test/下的所有是名字为.svn的目录删除。--onlydir表示只操作目录。这个命令行对于需要清除从SVN目录中复制出来的目录中svn相关信息比较有用。

简单过程演示删除.svn过程:

待处理的目录:F:/project/test.


例如其中res下包含.svn目录:


执行Python脚本过程:


删除.svn后的目录(示例):



 

 

1.3提取文件

在某些场合可能想提取某个庞大目录中部分类型文件,可以使用extract操作。在此处,脚本中采用copy方式实现此操作,不会去更改到原有的目录树结构。如果用户想在提取文件后,删除原来的文件,可以再对之进行delete操作。

 

示例:

flow.py--extract *.java --dir F:/project/test --extractdir F:/project/testjavabackup

提取F:/project/test目录下的所有的java文件到F:/project/testjavabackup目录下,在新目录下会维持原来的目录结构(层次保持一致)。

简单演示提取文件过程:

提取文件执行命令:


 执行后的结果:


 


2  源码实现

Python源码:

 

# -*- coding:utf-8 -*-
#This program is used for routine operations.
#----------------------------------------
#Process a bunch of files or directories:
#1)change names.
#2)delete files or dir.
#3)extract certain files or dir.
#----------------------------------------
#Time:2012-8-27 21:39:36
#Author:Aspiration
from __future__ import print_function
import os,re,sys
import stat
import time
import errno
import shutil


usage='''
Input cmdline format: 
"flow.py [--options] --changename filename newname --dir directory 
"flow.py [--options] --delete  filename --dir directory 
"flow.py [--options] --extract  filename --dir directory --extractdir extractdir

options meaning:
--re:use regular expression to search files.
  It's optional.
--cd:operations will influence directory(changename/delete).
  It's optional. 
--onlyddir:Only operate on directories,not influence regular files.
  It's optional.
--changename:change file or directory names.
  Newname can use special syntax:
  %D:current date;%T:current time;%C:increase counter.
  Example:"--change test*.txt test%D-%T.txt"
  this command line will use date and time to rename files. 
--delete:delete files.
--extract:extract certain files in directory.
--dir: operate on which directory.
--extractdir:this option is for extract operation providing destination directory
  for 

Example:
1)flow.py --changename test*.txt test_%C.txt --dir ./testdir/
  meaning:rename all txt files with header(test) to test_1.txt test_2.txt ...
2)flow.py --onlydir --delete .svn ./
  meaning:delete all svn related directory in current directory.
'''

def remove_readonly(func, path, exc):
  excvalue = exc[1]
  if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
  
    # ensure parent directory is writeable too
    pardir = os.path.abspath(os.path.join(path, os.path.pardir))
    if not os.access(pardir, os.W_OK):
      os.chmod(pardir, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO)

    os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
    func(path)
  else:
    raise

class Ops():
  chopt="--changename"
  delopt="--delete"
  exopt="--extract"

  def __init__(self):
    self.wildcard=True    #default supporting wildcard.
    self.cd=False         #change directory.
    self.cf=True          #change files.
    self.operation=""   
    self.count=0          #record total operations numbers.
    self.argsanity=True   #indicate whether arguments are sane.
    
    self.dir=""
    self.filename=""
    self.newname=""
    self.extractdir="./extract/"

    #Parse detailed command line arguments. 
    self.parseargs()
    
    if self.dir=="":
      print("Error! You should specify directory with --dir xxx")
      self.argsanity=False
    if self.operation=="":
      print("Error! You should specify operation.")
      self.argsanity=False

      
  def run(self):
    
    if self.argsanity==False:
      return

    #Here we process wildcard case.
    try:
      tempname=self.filename
      if self.wildcard==True:
        tempname=re.escape(tempname)
        tempname=str(tempname).replace("\*",".*")
        tempname=str(tempname).replace("\?",".")
      tempname="^"+tempname+"$"
      regex=re.compile(tempname)  
    except re.error,e:
      print("Regular expression isn't correct.")
      #print(e)
      exit(1)
    except:
      print("Error!")
      exit(1)

    #Workhorse runs here.
    try:
      if self.operation==Ops.chopt:
        self.changename(regex,self.newname,self.dir)
      elif self.operation==Ops.delopt:
        self.deletefile(regex,self.dir)
      elif self.operation==Ops.exopt:
        self.extractfile(regex,self.dir,self.extractdir)
    except OSError,e:
      print("Error:Operating System call can't be OK!")
      if isinstance(e,WindowsError):
        print("WindowsError:"+str(e))
      else:
        print(e)
      exit(1)
    except:
      print("Error!")
      raise
      #exit(1)

      
  def changename(self,oldregex,newname,directory):
    '''changename() is to change file or directory names.
      It support normal names or wildcard or regular expression.
    '''
    if oldregex==newname:
      return
    if not os.path.isdir(directory):
      print("Directory:"+str(directory)+" doesn't exist!")
      return
    identicalcnt=0
    files=os.listdir(directory)
    for f in files:
      #print("file:"+str(f))
      path=directory+os.sep+str(f)
      #print("path:"+path)
      if os.path.isdir(path):
        self.changename(oldregex,newname,path)
      if (self.cf and os.path.isfile(path)) or (self.cd and os.path.isdir(path)):
        if re.match(oldregex,f):
          self.count=self.count+1
          #print("self.count="+str(self.count))
          #print("filename:"+str(f))
          tempname=newname
          tempname=self.parsename(tempname)
          tempname=re.sub(oldregex,tempname,str(f))
          #print("newname:"+str(tempname))
          newpath,tempname,identicalcnt=self.getnewfilepath(\
            directory,tempname,identicalcnt)
          #print("newpath:"+newpath)
          os.rename(path,newpath)
          print("change file {0} to {1}".format(path,tempname))
      
          
  def deletefile(self,fileregex,directory):
    '''deletefile() is to delete file or directory.
      It support normal names or wildcard or regular expression.
    '''
    if not os.path.isdir(directory):
      print("Directory:"+str(directory)+" doesn't exist!")
      return
    files=os.listdir(directory)
    for f in files:
      #print("file:"+str(f))
      path=directory+os.sep+str(f)
      #print("path:"+path)
      if self.cf and os.path.isfile(path):
        if re.match(fileregex,f)!=None:
          self.count=self.count+1
          try:
            os.remove(path)
          except:
            if not os.access(path,os.W_OK):
              os.chmod(path,stat.S_IRWXU|stat.S_IRWXG|stat.S_IRWXO)
              os.remove(path)
              print("The file isn't allowed to delete.")
            else:
              print("Error! remove:"+path)
          else:
            print("delete {0} ".format(path))
      if os.path.isdir(path):
        if self.cd and re.match(fileregex,f)!=None:
          self.count=self.count+1
          shutil.rmtree(path,ignore_errors=False, οnerrοr=remove_readonly)
          print("delete dir {0} ".format(path))
        else:
          self.deletefile(fileregex,path)


  def extractfile(self,fileregex,directory,newdirectory):
    '''extractfile() is to extract file or directory to specified dir.
      It support normal names or wildcard or regular expression.
      The extracted files will remain the orignal hierarchy.
    '''
    if not os.path.isdir(directory):
      print("Directory:"+str(directory)+" doesn't exist!")
      return
    files=os.listdir(directory)
    for f in files:
      #print("file:"+str(f))
      path=directory+os.sep+str(f)
      newpath=newdirectory+os.sep+str(f)
      #print("path:"+path)
      #print("newpath:"+newpath)
      if os.path.isdir(path):
        if self.cd and re.match(fileregex,f)!=None:
          self.count=self.count+1
          if os.path.exists(newpath):
            shutil.rmtree(newpath)          
          shutil.copytree(path,newpath)
          print("copy dir {0} to {1} ".format(f,newdirectory))
        else:
          self.extractfile(fileregex,path,newpath)
      elif os.path.isfile(path):
        if self.cf and re.match(fileregex,f)!=None:
          self.count=self.count+1
          #print("match:"+f)
          if not os.path.exists(newdirectory):
            os.makedirs(newdirectory)
          shutil.copy2(path,newdirectory)
          print("copy {0} to {1} ".format(f,newdirectory))
      else:
        print("Error! We can only process files and directories!")

  #parse command line arguments.      
  def parseargs(self):
    argnum=len(sys.argv)

    #we need at least 5 args,
    #E.g."flow.py --delete test --dir ./test"
    if argnum <5:
      print(usage)
      return

    i=1
    try:
      while(i<argnum):
        if sys.argv[i][:2]=="--":   #Options
          if sys.argv[i]==Ops.chopt:
            self.operation=sys.argv[i]
            self.filename=sys.argv[i+1]
            self.newname=sys.argv[i+2]
            i=i+2
          elif sys.argv[i]==Ops.delopt:
            self.operation=sys.argv[i]
            self.filename=sys.argv[i+1]
            i=i+1
          elif sys.argv[i]==Ops.exopt:
            self.operation=sys.argv[i]
            self.filename=sys.argv[i+1]
            i=i+1
          elif sys.argv[i]=="--dir":
            self.dir=sys.argv[i+1]
            i=i+1
          elif sys.argv[i]=="--re":
            self.wildcard=False
          elif sys.argv[i]=="--cd":
            self.cd=True
          elif sys.argv[i]=="--onlydir":
            self.cf=False
            self.cd=True
          elif sys.argv[i]=="--extractdir":
            if self.operation==Ops.exopt:
              self.extractdir=sys.argv[i+1]
              i=i+1
            else:
              print("Error! only extract can have --extractdir option")
              return
          else:
            print("Error! unknown options:"+sys.argv[i])
            return
        else:   #if not options,we ignore them.
          print("unknown commandline data:"+sys.argv[i])
        i=i+1
    except Exception,e:
      print("Error! command line format isn't correct.")
      #print(usage)
      exit(1)
    
    #for arg in sys.argv:
    #  print(arg,end=' ')
    #print(" ")
    
  #Parse special symbol input from command line.  
  def parsename(self,namepattern):
    if str(namepattern).find("%C")!=-1:
      #error: here the count should not be self.count
      namepattern=str(namepattern).replace("%C",str(self.count),1)
    if str(namepattern).find("%T")!=-1:
      curtime=time.strftime("%H%M%S",time.localtime())
      # print("time:"+str(curtime))
      namepattern=str(namepattern).replace("%T",curtime,1)
    if str(namepattern).find("%D")!=-1:
      curdate=time.strftime("%Y%m%d",time.localtime())
      # print("date:"+str(curdate))
      namepattern=str(namepattern).replace("%D",curdate,1)
    return namepattern

  #get available filepath for a file.
  #Mainly to check existed files with same name.
  def getnewfilepath(self,parentdir,filename,cnt):
    newfilepath=parentdir+os.sep+str(filename)
    if os.path.exists(newfilepath):
      parts=str(filename).rsplit(".",1)
      #print(parts)
      while os.path.exists(newfilepath):
        cnt=cnt+1
        #print("cnt:"+str(cnt))
        if len(parts)==1:
          newfilepath=newfilepath+str(cnt)
        else:
          filename=parts[0]+str(cnt)+"."+parts[1]
          newfilepath=parentdir+os.sep+filename
    return (newfilepath,filename,cnt)

  
if __name__=="__main__":
  routineops=Ops();
  print("\nRESULT")
  print("--------------------------")
  routineops.run()

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值