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
BeastOfCaerbannog
  • 12,964
  • 10
  • 49
  • 77
raul
  • 131
  • 1
  • 1
  • 5
  • 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
  • 42,315
  • 7
  • 94
  • 147
  • 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](https://askubuntu.com/a/1185438/618353). – 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
    @user3140225 [This explains the differences in `ls -s` and `ls -a`.](https://askubuntu.com/a/269531/167115) – mchid Nov 04 '19 at 11:04
  • 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" *
    
BeastOfCaerbannog
  • 12,964
  • 10
  • 49
  • 77
xenoid
  • 5,376
  • 2
  • 16
  • 34
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.

BeastOfCaerbannog
  • 12,964
  • 10
  • 49
  • 77
  • ```-s``` will show you allocated blocks not bytes, one block is 4096 bytes. –  Nov 01 '19 at 10:34
  • [wikipedia](https://en.wikipedia.org/wiki/Block_(data_storage)) "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.

WinEunuuchs2Unix
  • 99,709
  • 34
  • 237
  • 401
  • 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
  • 106
  • 10
1

Command:

ls -l | awk '{print $5, $9}'
chiateng
  • 11
  • 1
  • 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