├── README.md └── ducount /README.md: -------------------------------------------------------------------------------- 1 | # ducount - disk usage count 2 | Calculates disk usage for files in a given directory. 3 | 4 | It mimics the functionality of the unix du command by recursively scanning files in 5 | the target directory. It will additionally display file count and age information. 6 | 7 | Usage: ducount [PATH] 8 | Columns are printed in the following order: 9 | - size 10 | - count 11 | - age of youngest file in dir (recursive) 12 | - dir or file 13 | - path 14 | 15 | ### Example Usage 16 | The below example runs ducount on a directory called reports. The results are a list of subdirectories showing how many KB each one sums to, the total file count of each one, and the age of the youngest file. The totals of the reports directory itself are shown as the last row. 17 | ``` 18 | $ ducount reports/ 19 | 152.35 KB 14 500 days old d reports/archived 20 | 299.55 KB 72 28 days old d reports/last_month 21 | 404.06 KB 45 5 days old d reports/last_week 22 | 434.2 KB 132 2 days old d reports/this_week 23 | 580.39 KB 36 1 days old d reports/yesterday 24 | 817.41 KB 21 0 days old d reports/today 25 | 12.11 MB 811 0 days old d reports/current 26 | 14.74 MB 1138 . 27 | ``` 28 | 29 | ### Install 30 | Paste the following line into a terminal to install: 31 | ``` 32 | sudo wget -O/usr/bin/ducount https://raw.githubusercontent.com/simon-thorpe/ducount/master/ducount && sudo chmod +x /usr/bin/ducount 33 | ``` 34 | 35 | ### Run 36 | Then run in your directory of choice: 37 | ``` 38 | $ ducount 39 | ``` 40 | -------------------------------------------------------------------------------- /ducount: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2015, Simon Thorpe 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | import os,sys,math,datetime,termcolor 28 | def get_dir_filesize_recursive(item,youngest,readable): 29 | r=0 30 | if not os.access(item,os.R_OK): 31 | return (r,youngest,False) 32 | islink=os.path.islink(item) 33 | if os.path.isdir(item) and not islink: 34 | for child in os.listdir(item): 35 | o=get_dir_filesize_recursive(item+'/'+child,youngest,readable) 36 | if not o[2]: 37 | readable=False 38 | r+=o[0] 39 | if youngest==None or o[1]>youngest: 40 | youngest=o[1] 41 | else: # is file 42 | if not islink: 43 | r=os.path.getsize(item) 44 | d=os.path.getmtime(item) 45 | youngest=d 46 | return (r,youngest,readable) 47 | 48 | def human_readable_filesize(v): 49 | v=float(v) 50 | if(v>=1024*1024*1024*1024*1024): 51 | return str(math.floor(v/(1024*1024*1024*1024*1024)*100)/100)+" PB" 52 | elif(v>=1024*1024*1024*1024): 53 | return str(math.floor(v/(1024*1024*1024*1024)*100)/100)+" TB" 54 | elif(v>=1024*1024*1024): 55 | return str(math.floor(v/(1024*1024*1024)*100)/100)+" GB" 56 | elif(v>=1024*1024): 57 | return str(math.floor(v/(1024*1024)*100)/100)+" MB" 58 | elif(v>=1024): 59 | return str(math.floor(v/1024*100)/100)+" KB" 60 | else: 61 | return str(int(v))+" bytes" 62 | 63 | def printc(text,color=None,blink=None): 64 | if color: 65 | attrs=None 66 | if blink: 67 | attrs=['blink','bold'] 68 | print(termcolor.colored(text,color,attrs=attrs)) 69 | else: 70 | print(text) 71 | 72 | if __name__=='__main__': 73 | if '--help' in sys.argv: 74 | sys.stderr.write('ducount - disk usage count\n') 75 | sys.stderr.write('Calculates disk usage for files in a given directory.\n') 76 | sys.stderr.write('It mimics the functionality of the unix du command by recursively scanning files in\n') 77 | sys.stderr.write('the target directory. It will additionally display file count and age information.\n') 78 | sys.stderr.write('\n') 79 | sys.stderr.write('Usage: ducount [PATH]\n') 80 | sys.stderr.write('Columns are printed in the following order:\n') 81 | sys.stderr.write('- size\n') 82 | sys.stderr.write('- count\n') 83 | sys.stderr.write('- age of youngest file in dir (recursive)\n') 84 | sys.stderr.write('- dir or file\n') 85 | sys.stderr.write('- path\n') 86 | exit() 87 | elif '--version' in sys.argv: 88 | print('ducount version 1.2.0') 89 | exit() 90 | path='.' 91 | if len(sys.argv)>1: 92 | path=sys.argv[-1] 93 | items=os.listdir(path) 94 | totalCount=len(items) 95 | totalSize=0 96 | results=[] 97 | for item in items: 98 | item=path.rstrip('/')+'/'+item 99 | fileSize,fileTime,readable=get_dir_filesize_recursive(item,None,True) 100 | l=0 101 | for r,d,f in os.walk(item): 102 | l+=len(d)+len(f) 103 | totalCount+=l 104 | totalSize+=fileSize 105 | fileType='-' 106 | if os.path.isdir(item): 107 | fileType='d' 108 | results.append((fileSize,l,item,fileTime,fileType,readable)) 109 | 110 | results=sorted(results,key=lambda x:x[0]) 111 | now=datetime.date.today() 112 | 113 | for result in results: 114 | ageText='?' 115 | if result[3] and result[5]: 116 | ageDays=(now-datetime.date.fromtimestamp(result[3])).days 117 | if ageDays<0: 118 | #ageText=str(ageDays*-1)+' days new' 119 | ageText='future day' 120 | else: 121 | ageText=str(ageDays)+' days old' 122 | color=None 123 | if not result[5]: 124 | color='red' 125 | printc(human_readable_filesize(result[0]).ljust(11)+' '+str(result[1]).ljust(9)+' '+ageText.ljust(14)+' '+str(result[4])+' '+str(result[2]),color) 126 | 127 | # totals 128 | print(human_readable_filesize(totalSize).ljust(11)+' '+str(totalCount).ljust(9)+' .') 129 | 130 | # display any warnings 131 | if list(filter(lambda x:not x[5],results)): 132 | printc('WARNING: Some files could not be scanned!','red',True) 133 | exit(1) 134 | --------------------------------------------------------------------------------