I have a script in which I use find command to filter out all png files in a given folder and list them along with their size. I want the output in the following format:

 someFile.png => 1.2K 

I'm using awk to to fetch $5(file size in human friendly format) and $9(path). Here is what I'm currently using:

 find "$somePath"  -iname "*.png" -print0 | xargs -0 ls -lh  | awk '{print $9,"=>",$5}'

Sometimes the file names or the paths have spaces and this trips the script. Here is an example output:
/Users/test/Desktop/Test/image1 => 1.2K
/Users/test/Desktop/Test/image1.png => 1.4K

The first entry in output is a file called "image1 test.png". I read somewhere that one should not parse the ls output. What's is the best way to tackle this?

  • 3
    I don't understand why you're invoking ls -l and then trying to extract just the pathname that was fed to ls in the first place.
    – geekosaur
    Apr 25 '12 at 21:38
  • Just use find options like -exec or -execdir (generally safer). It'd be helpful if you described exactly what you wanted to do for each file.
    – jw013
    Apr 25 '12 at 21:42
  • I modified the question. Sorry for that
    – smokinguns
    Apr 25 '12 at 21:43
  • 2
    @smokinguns That's not a useful edit. Describe in English what you are trying to accomplish. Don't make people try to guess your intent. From your shell snippet my best guess is that you want to display the file size for all PNG files located in a directory. The part of your snippet up to the awk already does that. If you don't plan on any further processing of the files, just use ls -sh instead of -lh, and get rid of the awk.
    – jw013
    Apr 25 '12 at 21:50
  • Instead of ls -sh, you can also use du --apparent-size -h (probable GNU coreutils specific extension).
    – jofel
    Apr 26 '12 at 8:46

Usually, find can be made to do what you want by using exec instead of piping the output to other programs.

find "$somePath" -iname "*.png" -printf '%p => %s\n'

If you really need human-friendly sizes, then it's slightly more complicated:

find "$somePath" -iname "*.png" -exec printf '{} => ' \; -exec sh -c 'ls -sh "{}" | cut -f1 -d\  ' \;

Note, the last one is parsing ls, but as far as I know, it's impossible for a space to show up in the first column; since we're only taking the first column here, and because we're operating on a single entry at a time (not a list), it's fine. Otherwise, you can use some other program to show the size. On BSD and Linux, you can use stat; on Solaris, you might have to write your own program to output only the size of a file.

The important part is that you can always use find to exec anything you want, passing it complete filenames - this way you're not trying to pull filenames out of a list that doesn't have good delimiters.

  • Both echo with options and stat are non-portable. It seems a bit premature to throw them out without knowing what the OP wants and what system / versions the OP has. But if you are going to make assumptions, it'd be better to suggest find ... -printf '%p => %s'.
    – jw013
    Apr 26 '12 at 0:26
  • @jw013 GNU find can't print human-friendly sizes. Apr 26 '12 at 1:19
  • @Shawn: I'm on Mac OSX. I get "find: -printf: unknown option" error
    – smokinguns
    Apr 26 '12 at 1:29
  • @smokinguns See '-exec printf {}...' in the second example. Apr 26 '12 at 1:32
  • @Gilles I suggested ls -sh in my comment on the question itself. However, my comment on this particular answer was a replacement for the pre-edited original, which used stat -c %s. Since the OP still hasn't told us what is actually desired, I didn't want to change @Shawn's answer unnecessarily. Of course that's moot anyways now that the OP has finally revealed the platform as OS X.
    – jw013
    Apr 26 '12 at 1:54

The most convenient way is to use printf from find, but it hasn't exactly the option you're looking for. %k prints the size in K, but the size, allocated on the disk, so depending on your settings, this might be always a multiple of 4k (like on my system) or something similar:

find  -name "C*.png" -printf "%f => %k \n"

CalculatorVergleich.png => 28 
ClassifierImage.png => 8 
ClassifierImage.png => 8 

There is an option with size in bytes:

find  -name "C*.png" -printf "%f => %s \n"
CalculatorVergleich.png => 26256 
ClassifierImage.png => 6313 
ClassifierImage.png => 6473 

And there is the %b for occupied 512b Blocks:

find  -name "C*.png" -printf "%f => %b  \n" 
CalculatorVergleich.png => 56  
ClassifierImage.png => 16  
ClassifierImage.png => 16  

Maybe it is useful for you? It shouldn't have problems with blanks in filenames.


I assume that you want to use awk to process ls -l for the additional information that ls -l provides. In general find and stat gives a more reproducible result, but feel free to use ls.

One of the nice things that ls -l does, is it has a fixed width, therefore you can use substr for the start of line, then not specify the length, which will include all of the characters till the end of the line.

In my below example I assign the file name to a string, then replace the spaces with underscores. I usually perform further analysis with awk, so purging the spaces from the file names makes the analysis easier, and the final result goes to humans.

$ ls -al | awk '{myfilename=substr($0, 57);gsub(/ /,"_",myfilename);print myfilename}'

With find I would usually crawl a file system in a several hour process, and save the stats of all of the files, then pull out key fields for later analysis.

find "$myPath" -type f  -print0  |  xargs -0 stat   -c"%W %w %Y %y %X %x %9s %n" > myTemp.txt
cat myTemp.txt | awk '{myfilename=substr($0, 152);gsub(/ /,"_",myfilename);printf "%s %s %12s %s\n", $6,$10,$13,myfilename}'

Also now that the spaces are underscores, I can sort the result on the filename without re-arranging the columns for the sort.

| sort -k 4

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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