32

Consider the following output from df.

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        23G  6.1G   16G  29% /
udev             10M     0   10M   0% /dev
tmpfs           397M  420K  397M   1% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           1.8G  904K  1.8G   1% /run/shm
/dev/sda6       890G  324G  521G  39% /home
/dev/sdb1       459G  267G  169G  62% /home/user/mnt
none            4.0K     0  4.0K   0% /sys/fs/cgroup

How can I only show lines that begin with "/dev" and keep the heading, but filter out everything else. I'd also like to not have to resort to using using temporary files or variables? Note: the heading is locale dependent, therefore you can't catch it with a regexp.

3
  • locale dependant maybe, but your can force the language with LANG=C df.
    – Totor
    Dec 19, 2013 at 15:46
  • @Totor see 1_CR's answer, that's exactly what he suggested.
    – terdon
    Dec 19, 2013 at 15:50
  • This has been answered several times previously. See e.g. body from unix.stackexchange.com/a/11859/3169
    – Mikel
    Feb 2, 2014 at 19:46

9 Answers 9

44

I would use a slightly more sophisticated approach than simple grep:

  1. awk

    df -h | awk 'NR==1 || /^\/dev/'
    

    NR is the current line number so the awk scriptlet above will print if this is the first line or if the current line begins with /dev. And after posting this I see it is the same as @1_CR's answer. Oh well...

  2. Perl

    df -h | perl -ne 'print if (/^\/dev/ || $.==1)'
    

    The same idea here, in Perl the special variable $. is the current line number. An alternative way would be

    df -h | perl -pe '$_="" unless /^\/dev/ || $.==1'
    

    The -p switch will print all lines of the input file. The value of the current line is held in $_ so we set $_ to empty unless we want the current line.

  3. sed

    df -h | sed -n '1p; /^\/dev/p'
    

    The -n suppresses normal output so no lines are printed. The 1p means print the first line and the /^\/dev/p means print any line that starts with /dev.

    As pointed out in the comments below, in the unlikely case where the locale on your current system causes the header line to start with /dev, the command above will print it twice. Stephane Chazelas points out that this one will not have that problem:

    df -h | sed  -e 1b -e '/^\/dev/!d'
    
  4. grep

    df -h | grep -E '^(/dev|File)'
    

    This might not be portable because of LOCALE problems as you said. However, I am reasonably certain that no locale or df version will give a path in the first line, so searching for lines that contain no / should also work:

    df -h | grep -E '^[^/]*$|^/dev'
    
1
  • Your sed one would print the 1st line twice if it started with /dev. Unlikely, but sed -e 1b -e '/^\/dev/!d' wouldn't have the problem. Dec 18, 2013 at 18:31
15

You are going to run into issues attempting to parse df output, however for simple cases, the following may work

LC_ALL=C df -P | awk 'NR == 1 || /^\/dev/' 
0
12

This should work as well (not tested, though):

df | (read a; echo "$a"; grep /dev)

or

df | (head -n 1; grep ^/dev)
4
  • 1
    very nice. ^/dev will limit this to lines that start with /dev. Wasn't able to do that as an an edit since I was short 9 characters Dec 20, 2013 at 17:02
  • 1
    First one works, but you should echo "$a" (note quotes) or any spacing in the header will shrink down to single spaces (eg with column aligned output). Second one does not.
    – steveayre
    Dec 16, 2015 at 15:25
  • @steveayre Thanks for the suggestion. Integrated it into answer. Dec 17, 2015 at 13:33
  • 1
    @steveayre even echo "$a" is not completely safe (possible different interpretation of - or \ ). It is always better to use printf %s\\n "$a" to print a string which could possibly contain the mentioned characters. Jan 9, 2020 at 10:55
5
df -h | tee >(head -1) >(sleep 0.5;grep ^/dev) > /dev/null;sleep 1.0
5
  • 2
    I just tried that; it printed the header line first. Dec 18, 2013 at 19:15
  • 1
    @KeithThompson Why is that a problem? The accepted answers also print the header line first.
    – l0b0
    Dec 19, 2013 at 11:49
  • @l0b0: Sorry, I mistyped; it printed the header line last. Dec 19, 2013 at 16:00
  • Worked correcly on my machine. Try df -h | tee >(head -1) >(sleep 0.5;grep ^/dev) > /dev/null;sleep 1.0 Dec 19, 2013 at 18:18
  • Anonymous edited my post and it does not work for me. df -h | tee >(head -n 1) | grep ^/dev Aug 26, 2015 at 16:14
4
df | head -n 1; df | grep ^/dev
3
  • Not bad, but it has the disadvantage that it invokes df twice. Dec 18, 2013 at 19:16
  • 3
    Are we short on df invocations?
    – sqqqrly
    Dec 18, 2013 at 19:33
  • Not particularly, but there are solutions like terdon's df -h | sed -e 1b -e '/^\/dev/!d' that only invoke df once. The df command can hang in some circumstances; in that case, invoking it just once is probably better. Dec 18, 2013 at 19:38
3
df | grep -n '' | grep -E '^1:|[^:]*:/dev' | sed 's/[^:]*://'

The grep -n inserts line numbers into the stream, we then pull out the first one and the lines of interest via egrep, then remove the added numbers from the output via sed.

You can avoid the sed by using positive lookbehinds (which grep can handle with its PCRE engine (-P)) and -o which tells grep to only print the matched pattern:

df | grep -n '' | grep -Po '(?<=^1:)(.*)|(?<=:)(/dev.*)'
3

Take the definition of body from sort but keep header line at the top

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

Use it like this

$ df -h | body grep ^/dev
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        23G  6.1G   16G  29% /
/dev/sda6       890G  324G  521G  39% /home
/dev/sdb1       459G  267G  169G  62% /home/user/mnt
2

Instead of filtering df output, you can filter its input.

Before:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
C:/Cygwin/bin   150G   76G   74G  51% /usr/bin
C:/Cygwin/lib   150G   76G   74G  51% /usr/lib
C:/Cygwin       150G   76G   74G  51% /
C:              150G   76G   74G  51% /cygdrive/c

After:

$ mount | grep ^C:/Cygwin | cut -d' ' -f3 | xargs df -h
Filesystem      Size  Used Avail Use% Mounted on
C:/Cygwin/bin   150G   76G   74G  51% /usr/bin
C:/Cygwin/lib   150G   76G   74G  51% /usr/lib
C:/Cygwin       150G   76G   74G  51% /

UPDATE 2014-01-24 08:03 UTC: Only grep for C:/Cygwin in the beginning of a line

2
  • Nice trick but it will not work as expected if you are mounting by UUID instead of device name. The OP wants df style output which means /dev/foo your command will return /dev/disk/by-uuid/ format output unless fstab contains the /dev name.
    – terdon
    Dec 19, 2013 at 16:29
  • The mount command uses /etc/mtab, which is updated dynamically, and while I am not sure where df gets its data from, I suspect that on Linux it reads /proc/mounts. Either way I don't understand how fstab is relevant and could not reproduce the problem with UUID-mounted disks. Can you elaborate on it?
    – mkalkov
    Jan 24, 2014 at 8:00
-3
df -h | egrep -e Filesystem -e "/dev" -v
2
  • OP has said the heading is locale-dependent, so you can't match it like that. And what is the sda1 doing at the end?
    – derobert
    Dec 20, 2013 at 20:06
  • @syntaxerror the heading is locale-dependent, as is sorting. So that won't work either, for the same reason. Try LANGUAGE=ja df -h, for example. Or Spanish, or French, or many others.
    – derobert
    Jan 7, 2015 at 16:24

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