39

My goal is deceptively simple (at least to me). I wish to take the output of ls -l or ls -lh and select just one field.

I am looking for this to be as bulletproof as possible, by which I mean, assume that filenames can have a variable number of spaces, not everything in the field has the same length, etc.

Bonus points for having a script that will take the name of the the field (or even just a field number), and then return the contents of the field.

I want to turn

enter image description here

into:

enter image description here

ish
  • 139,926
soandos
  • 1,155

4 Answers4

64

Try ls -l | awk '{print $7}'.

awk selects columns so it's perfect for this task.

Eliah Kagan
  • 117,780
  • 4
    As explained by @ormaaj: Do not rely on pieces of information being available in specific places with ls. The find command is tremendously helpful. – Paddy Landau Jul 18 '12 at 10:02
  • @PaddyLandau How do I use the find command to print a file size in megabytes like I can with ls -l --block-size=M? – SurpriseDog Jul 04 '19 at 18:08
  • @Benjamin — The ls command is not POSIX standard, so a script that depends on ls could break on different systems or even over time. See the answer by @ormaaj on this page for more details. One way to do what you ask is with a Bash loop. Here is an example. for FILENAME in *; do FILESIZE=$(( $( stat --format=%s "${FILENAME}" ) / 1024 )); echo "${FILENAME}" ${FILESIZE}; done. Of course, instead of echo, you'd use your processing. – Paddy Landau Jul 05 '19 at 07:30
  • @PaddyLandau I just ended up piping the output of the find command to ... | xargs -d '\n' du -m which works just as well. – SurpriseDog Jul 05 '19 at 16:13
  • @Benjamin You might want to look at the -exec option of find. – Paddy Landau Jul 05 '19 at 19:08
  • This is lowely! One question, how to keep color of ls command? – Nam G VU Dec 03 '20 at 04:14
25

Never parse ls. Use GNU find. Or if portability isn't important, stat(1).

find . -maxdepth 1 -printf '%Td\n'

For reading data other than lists of filenames line-by-line and splitting into fields, see: BashFAQ/001

There are no methods to reliably read a newline-delimited list of filenames that make sense under most circumstances.

ormaaj
  • 351
3

You may fetch the specific column in shell like:

ls -al | while read perm bsize user group size month day time file; do echo $day; done

or awk as shown in @Corey answer, cut -c44-45 would also work after adjustment (since ls has fixed columns) , or whatever else, however the main problem is that it won't be reliable and bulletproof (e.g. on Unix it may be $6, not $7, and it changes depending on arguments) making it not machine-friendly therefore it is not recommended to parse ls command at all.

The best is to use different available commands such as find or stat, which can provide relevant options to format the output as you need. For example:

$ stat -c "%x %n" *
2016-04-10 04:53:07.000000000 +0100 001.txt
2016-04-10 05:08:42.000000000 +0100 7c1c.txt

To return column of only days of modifications, try this example:

stat -c "%x" * | while read ymd; do date --date="$ymd" "+%d"; done

It's worth to note that GNU stat could have different options to BSD stat, so it still won't be bulletproof across different operating systems.

kenorb
  • 10,347
  • 1
    +1 for mention that ls should not be parsed as well as for use of stat . date however is setup incorrectly. You should add --date=$ymd , since date by itself will print the current day, but the purpose here is to convert date format of the file – Sergiy Kolodyazhnyy Apr 10 '16 at 04:37
  • The ls -la | cut -c32-33 command in total honesty is just completely unreliable, not only because of the possible pitfalls with the filenames, but simply because it depends on the usernames' length and on the files' size. The stat -c "%x" *.* command looks ok, but by using *.* you're restricing it only to filenames containing a dot. I guess the intention was to catch also hidden files; in that case you should enable globbing for dotfiles beforehand and use * instead of *.*: shopt -s dotglob; stat -c "%x" * | [...]. – kos Apr 10 '16 at 04:50
  • Ah and what Serg said also, you should add --date="$ymd" to the date command, otherwise it will print the current day of the month. – kos Apr 10 '16 at 05:01
  • Thanks for the comments. I've addressed the issues and provided a better initial example. – kenorb Apr 10 '16 at 12:11
0

I need to catalog my installed applications prior to backing up and upgrading and encountered this issue trying to figure out how to trim ls output, but the awk applications suggested in other Ask Ubuntu questions didn't work at all.

What worked for me were suggestions to use cut: I redirected test ls output to a text file and used a "ruler" in Sumblime Text to find the character column numbers

total 2060
         1         2         3         4         5
123456789012345678901234567890123456789012345678901234567890
-rw-r--r-- 1 root    root      291 2023-04-12 13:53 apport-gtk.desktop
-rw-r--r-- 1 root    root      277 2023-04-12 13:53 apport-kde-mime.desktop
-rw-r--r-- 1 root    root     3417 2018-04-14 03:38 assistant-qt5.desktop
-rw-r--r-- 1 root    root     1066 2022-03-07 19:03 atom.desktop
-rw-r--r-- 1 root    root     2683 2018-05-01 04:10 audio-recorder.desktop
...

and then filtered with cut -c30-, where the trailing hyphen extends rendering to the end of (variable length) lines.

$ dir -l --time-style=long-iso /usr/share/applications | cut -c30-

291 2023-04-12 13:53 apport-gtk.desktop 277 2023-04-12 13:53 apport-kde-mime.desktop 3417 2018-04-14 03:38 assistant-qt5.desktop 1066 2022-03-07 19:03 atom.desktop 2683 2018-05-01 04:10 audio-recorder.desktop ...

This "ruler check" is needed since ls's field alignment is relative after the first access permissions field. For the specific issue here (isolating the day of month) we can identify columns 38 and 39 as our targets.

$ ls -l >> cutcheck.txt
$ cat cutcheck.txt
         1         2         3         4         5
123456789012345678901234567890123456789012345678901234567890
total 480
-rwxrwx--- 1 root plugdev    112 Mar 12 17:36 cutcheck.txt
-rwxrwx--- 1 root plugdev    112 Mar 12 17:34 cutruler.txt
drwxrwx--- 3 root plugdev  32768 Mar 10 17:53 KDEAppLaucherConfig
-rwxrwx--- 1 root plugdev 195385 Mar  2 18:02 KDEAppLaucherMenuEditorHandbk.pdf
-rwxrwx--- 1 root plugdev   2202 Mar 11 18:30 KDEAppLauncherFiles.txt
-rwxrwx--- 1 root plugdev  40346 Mar  5 21:10 KDEApps.ods
-rwxrwx--- 1 root plugdev  90993 Mar  5 20:12 KDEMenuAppsDesktopFiles.html
$ ls -l | cut -c38-39

12 12 10 2 11 5 5

Once you know the "ballpark" character column values for your particular file system, you can issue succesive cut commands to isolate what you need to extract.

The stat command allows specifying particular ls/dir output fields, but the neat ls column alignment is lost because of the variable field lengths. The application of awk given in @Corey Whitaker's answer did work, but with the same loss of column alignment.

I think stat is the better choice if the intention is direct import into a spreadsheet catalog because the field codes can be separated with commas for redirection to a CSV file.

haleba
  • 1