12

Suppose my directory, Historic_Documents has the following files/sizes:

  • Declaration_of_Independence.txt 342 bytes
  • Magna_Carta.txt 1580 bytes
  • Treaty_of_Versaille.txt 752 bytes.

Is it possible using only the ls command to get following output?

342 Declaration_of_Independence.txt
1580 Magna_Carta.txt
752 Treaty_of_Versaille.txt
raul
  • 131
  • 2
    find -maxdepth 1 -type f -printf '%s %f\n' –  Nov 01 '19 at 03:11
  • 1
    Is it possible using only ( and strictly only ) the ls options? No. Is it possible to get such output ? Yes, either with what bac0n suggested or with du command. That's exactly the output that du produces. So the question becomes, what is really the purpose ? To use ls or to get the right output ? What is the output for ? Viewing ? Passing to another command or saving somewhere ? – Sergiy Kolodyazhnyy Nov 02 '19 at 06:07
  • @SergiyKolodyazhnyy What about ls -sd --block-size=1 --format=single-column * – mchid Nov 04 '19 at 01:20
  • 1
    @mchid May work fine. -s is the allocated size in blocks, so will differ from what du may report for the same file (that is what is allocated for the file vs what the file actually is in bytes), and --block-size=1 is GNU specific option. But technically it fits what this question asks. So +1'ed your answer. – Sergiy Kolodyazhnyy Nov 04 '19 at 04:32

7 Answers7

14

How to parse output from ls -l verbose listing with a while loop:
I looked at this question and thought OK this is something you can do fairly easily with find:

$ find -maxdepth 1 -type f -printf '%s %f\n'

Or

#!/bin/bash
for i in *; do
    stat --format '%s %N' "$i"
done

(%N will honor QUOTING_STYLE environment variable; default is 'shell-escape').

Only way to get size from ls is through verbose listing. By using read to read ls output; leading and trailing spaces will be removed from the word before assigned to a variable, the first word is assigned to the first variable and so on, when all variables are assigned, the remaining words will be added to the last variable, that way we can preserve spaces on the filename.

#!/bin/bash
while read -r p c u g s e n; do
    [[ $p = total ]] && continue || { echo -n "$s "; eval "ls -d $n"; }
done < <(ls -Lpl --time-style=+%s --quoting-style=shell-escape)

(using echo -n "$s "; eval "ls -d $n" is a bit excessive, but it shows how to use 'shell-escape' with other commands. If you just want to show the output for size and name, use echo "$s $n" instead)

read variables:

-rwxr-xr-x 1 bac0n bac0n 209 1572207800 script.sh
     ^     ^   ^     ^    ^      ^         ^
     p     c   u     g    s      e         n

ls options:

  • -L when showing file information for a symbolic link, show information for the file the link references rather than for the link itself.
  • -p append / indicator to directories.
  • -l use a long listing format.
  • --time-style time/date format with -l; its a good practice to set this to epoch in scripts.
  • --quoting-style quoting non-printable characters using the POSIX proposed ‘$''’ syntax.

test directory:

$ ls
a  'b'$'\r''c'   'd'$'\n''e'  "f'g"   h,i  'j k'  'l  m'  'n  '  '  o'
12

Using only the ls options, you can do this:

ls -sd --block-size=1 --format=single-column *

Here are the options:

  • -sd says to print the allocated file size in blocks and the d option removes the directory "total" line from the output

  • --block-size=1 prints 1 byte per size unit (instead of K)

  • --format=single-column says to print the results as a single column

  • * says to run ls on all files in the current directory and is needed when using the d option

This assumes your current directory is Historic_Documents.


EDIT:

Because you want the size of the file contents, I don't think you can do this with only ls options. However, you can use du to get the exact result you desire:

du -b *

The -b or --bytes option prints the actual file size in bytes which is also equivalent to the options: --apparent-size --block-size=1.

Apparent size is the size of the file (the similar to the sizes listed by ls -l) and not the allocated file size or disk usage.

mchid
  • 43,546
  • 8
  • 97
  • 150
  • This does not show the same file sizes as ls -l does. For example, I have some files that their size is 218, 827, 283 in ls -l, but this command shows them all as 4096. I also experimented with these ls options, but I could not get the file sizes to be the same as with ls -l, so I ended up piping the ls output to other commands, as shown in my answer. – BeastOfCaerbannog Nov 04 '19 at 09:05
  • 2
    @user3140225 ls -s prints how much space the file takes on the filesystem and ls -l prints how much data the file contains. This method produces the same format asked for in the question using only ls options. Until the OP specifies what they mean by "size" the question is ambiguous. – mchid Nov 04 '19 at 10:47
  • @user3140225 The lowest my system will show is 2048. The OP does show some pretty small file sizes but they also ask for an answer that only uses ls options. We can compromise one way or the other (using a pipe or showing the size of how much space the file occupies on the filesystem). Naturally, my first thought would be to use sed or awk '{print $5 " " $9}' but I decided on only using ls options instead (my awk command would not account for blank spaces in the file names). – mchid Nov 04 '19 at 10:56
  • 1
  • 1
    Thanks for the link about the ls sizes! It's a good explanation. Indeed, however, we need more info from the OP on what they mean by "size". – BeastOfCaerbannog Nov 04 '19 at 11:46
  • --block-size=1 does not mean 1 byte, block-size scales the size, (SIZE / 1) –  Nov 04 '19 at 12:44
  • @bac0n The -s option is the reason it prints in blocks. Setting --block-size=1 will print the result in bytes instead of kilobytes. This is the difference between the size unit and the actual block size. This prints the allocated file size in bytes which is a multiple of the block size. You are correct that it does scale the size but for the purpose here it is to print in bytes instead of kilobytes. Just like the purpose of the d option here is to remove the "total" line from the output. – mchid Nov 04 '19 at 13:49
  • no it does not block-size scales the SIZE... try block-size=2 and you size will be divided by 2... it does not change unit block-size=K is just short for SIZE/1024 and so on. example if your BLOCKSIZE is 4096 and you set block-size=4K, ls will show 1 file.txt –  Nov 04 '19 at 14:16
  • @mchid, user3140225 I mean the amount of data the file contains. – raul Nov 05 '19 at 03:00
  • 1
    @raul Because you want the size of the file contents, I don't think you can do this with only ls options. However, you can use du -b * to get the exact result you desire. I edited the answer. – mchid Nov 05 '19 at 03:18
  • 2
    @raul Finding file size is a little more complex in practice. Most files are simple: filesystem allocates X number of blocks and then there's actual amount of data in those blocks. There's also sparse files - the filesystem allocates bunch of blocks for them, but there may be no data in them. Do for example truncate -s 1024 sparcefile.txt. The ls -l sparcefile.txt will tell you the file size is 1024, but there's no data in it and nothing is actually stored on disk, just brief metadata. – Sergiy Kolodyazhnyy Nov 05 '19 at 06:57
  • 1
    @raul So du --block-size=1 is what you need for many files and their actual size on disk,dd of=/dev/null if=somefile.txt bs=1 for just one specific file, and du -b for those sparse files (-b is same as --apparent-size --block-size=1). – Sergiy Kolodyazhnyy Nov 05 '19 at 07:05
5

With just ls, no, but with stat (if you have ls you have stat):

stat -c "%s %n" *
  • stat with a format (-c, --format) is the most flexible way to obtain file data
  • Although not explicitly stated in the man page, you can use width & precision specifiers on the fields, for instance to have the size listed on 8 columns:

    stat -c "%8s %n" *
    
xenoid
  • 5,504
3

Update 2

Here is a one-liner that uses awk, which has the benefit of also retaining multiple consecutive spaces in the filenames:

ls -l | awk 'BEGIN{FPAT="([[:space:]]*[^[:space:]]+)"; OFS = "";} FNR == 1 {next} {$1=$2=$3=$4=$6=$7=$8=""}1'
  • FPAT="([[:space:]]*[^[:space:]]+)" is used to set a regular expression of what a field should be. Here we set the field to have zero or more whitespace characters and at least one of every other character except whitespace. The suggestion about using FPAT was found in this Stack Overflow answer.

  • OFS = "" is used so that the output will only have a single space as a field separator (another one is added by the text manipulation).

  • FNR == 1 {next} is used so that a blank line at the beginning, that corresponds to the first line of the ls -l output that shows the total number of blocks used, won't be printed.

  • {$1=$2=$3=$4=$6=$7=$8=""}1 empties the specified fields and prints the rest, i.e. the fifth field (file size) and the ninth till the end (filename).

It should be noted that each line of the output of the above command will have one or more leading spaces. In my opinion this makes the output easier to read, however, you can remove the leading spaces by piping the awk command to sed as follows (remove the * from the sed command to remove just one leading space):

ls -l | awk 'BEGIN{FPAT="([[:space:]]*[^[:space:]]+)"; OFS = "";} FNR == 1 {next} {$1=$2=$3=$4=$6=$7=$8=""}1' | sed 's/^ *//'

Update

A better approach would be to use cut on the ls -l output:

ls -l | tr -s ' ' | cut -d ' ' -f 5,9-

Here, tr -s ' ' is used to squeeze multiple spaces between the fields of the ls -l output to one and then cut uses space as a delimiter to display the fifth field (file size) and all fields after the ninth (filename) of the ls -l output.

Note, however, that, if you have filenames with multiple consecutive spaces, they will be displayed as having single spaces, due to tr (thanks bac0n).


You can use this:

ls -sh
  • -s (or --size): prints the allocated size of each file, in blocks.
  • -h (or --human-readable) prints the size in a human readable format (i.e using K, M, etc. for thousands, millions, etc. respectively).

You can read the ls manpage for more info by running man ls in your terminal or by visiting the Ubuntu ls manpage online.

  • -s will show you allocated blocks not bytes, one block is 4096 bytes. –  Nov 01 '19 at 10:34
  • wikipedia "a block, sometimes called a physical record, is a sequence of bytes or bits", its a allocation unit, not bytes as in "size" -h will just scale block size to 4K (4096). –  Nov 01 '19 at 11:01
  • @bac0n Actually, -s flag's block size is 1024 bytes, as you can see if you compare ls -s and ls -s --block-size=1024. You are right on the fact that -s size is in blocks. However I am not sure that the ls -l output is indeed in MB and not blocks too. Nevertheless, I updated my answer with another option, which I think is better. Please check it out. – BeastOfCaerbannog Nov 01 '19 at 13:05
  • yes im sure and still block size is set by mkfs and you can find it with blockdev --getbsz /dev/sda1, if your blocksize is the same as a byte is just a coincidence. –  Nov 01 '19 at 13:17
  • @bac0n True. For me it's also 4096. But what I state in by comment above about ls -s is also true. I don't know why and I don't seem to be able to find out more, but if you can, please give me a ping. What do you think about the update in my answer? – BeastOfCaerbannog Nov 01 '19 at 13:23
  • if you store a file on the filesystem, it will be stored in a 4096-byte-block (or what ever blocksize you have chosen), that means, even if your file only contains 5 bytes it will still show 4096 bytes, if you change unit to MB and the precision rounds it to the same value does not make it right or the same. –  Nov 01 '19 at 13:33
  • 1
    latest update will fail on filenames with space and tr will manupulate filenames with double spaces –  Nov 01 '19 at 14:04
  • @bac0n You are right. Changing cut -d ' ' -f 5,9 to cut -d ' ' -f 5,9- prints filenames with a single space correctly. However tr will manipulate filenames with multiple spaces as you say. Your answer works great, although a bit more complex from what the OP would expect. After you added explanation about how the script works, it's a very good answer. Upvoted. – BeastOfCaerbannog Nov 03 '19 at 15:03
3

It's impossible to do with the ls command by itself. You can do it by piping ls -l output to other commands or with a totally different command like find as others have answered.

The tree command gives another alternative:

rick@alien:~/askubuntu$ tree -h
.
├── [8.8K]  aptfielout
├── [1.8K]  aptfilein
├── [ 435]  aptfileout
   (... SNIP ...)
├── [  38]  script
├── [4.0K]  subdir-A
│   ├── [  14]  1.mp4
│   ├── [  14]  2.mp4
│   ├── [  14]  3.mp4
│   └── [4.0K]  JSON
│       └── [4.0K]  JSON
│           ├── [   7]  1.json
│           ├── [   7]  2.json
│           ├── [   7]  3.json
│           └── [   7]  4.json
   (... SNIP ...)
├── [1.5K]  ttlus
└── [1.3K]  ttlus~

7 directories, 57 files

The tree option passed is -h for human readable size. For size in exact bytes pass -s.

  • Do you mind if I add another tree command that provides a different output style in my answer? – BeastOfCaerbannog Nov 04 '19 at 13:00
  • Normally people make a comment for different parameters but I don't know how significant your change is? – WinEunuuchs2Unix Nov 04 '19 at 13:18
  • It's just because it's not easy to explain the various options nicely formatted in a comment. Nevertheless, this is my suggestion: tree -L 1 -s -i --noreport. This makes tree list the current directory only and makes the output to more closely resemble the one from ls -l. – BeastOfCaerbannog Nov 04 '19 at 13:54
  • The OP doesn't need a max depth level as there are only three files in the directory. I wanted to illustrate how tree nicely indents subdirectories. I should have explained that... – WinEunuuchs2Unix Nov 04 '19 at 16:03
2
ls --size --format=single-column
abc
  • 116
1

Command:

ls -l | awk '{print $5, $9}'
  • 2
    It doesn't work properly if the filename contains blank spaces. You can [edit] your answer to improve it: I suggest also to add some details, an answer containing only a command is not considered a good qaulity answer. – Lorenz Keel Nov 03 '20 at 10:19