Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I want to find out what device my file is on so that I can use it in a script. I can get this far:

$ df  .
Filesystem   512-blocks      Used Available Capacity  Mounted on
/dev/disk0s2  498438976 294369520 203557456    60%    /

but this output feels too clumsy; is there a better way than parsing this to get the first 'word' of the second line?

What I really need is something like this so I can pipe it to the next command:

$ somecommand .
/dev/disk0s2

How can I achieve this, preferably without resorting to string hacking the 'df' output?

share|improve this question
4  
Parsing the df output is not difficult: df . | tail -1 | cut -f 1 -d " " But maybe there are better solutions. –  jofel Feb 22 '13 at 12:35
    
Nice, this works. I'm running it on 10'000s of files, so I will have to see what the performance is like VS just one pipe –  antonyh Feb 22 '13 at 13:15
2  
... the other way is to use stat, which gives a device field, but you'll have to translate that back. May be much faster though, especially if df is taking forever to get usage over, e.g., NFS. –  derobert Feb 22 '13 at 13:37
2  
stat -f "%Sdf" . seems quicker - it decreased the time from 1.8s to 1.7 over 500 iterations. I have no network concerns, but this is a top tip, thanks. –  antonyh Feb 22 '13 at 14:45
add comment

4 Answers

up vote 2 down vote accepted

You can do it with the shell alone (works in bash, dash, ksh, zsh):

df . | (read a; read a b; echo "$a")

Or if output is not needed (result will be kept in $a) and your shell supports process substitution (like bash, zsh):

{ read; read a b;}< <(df .)

And here are some comparisons with the other solutions' speed:

# pure shell solution 1

bash-4.2$ time for i in $(seq 500); do df . | (read a; read a b; echo "$a"); done > /dev/null
1.899

(dash) $ time -f '%e' dash -c 'for i in $(seq 500); do df . | (read a; read a b; echo "$a"); done > /dev/null'
1.05

(ksh) $ time for i in $(seq 500); do df . | (read a; read a b; echo "$a"); done > /dev/null
    0m1.16s real     0m0.02s user     0m0.12s system

(zsh) manatwork% time (for i in $(seq 500); do df . | (read a; read a b; echo "$a"); done > /dev/null)
1.51s

# pure shell solution 2

bash-4.2$ time for i in $(seq 500); do { read; read a b;}< <(df .); done
1.192

(zsh) manatwork% time (for i in $(seq 500); do { read; read a b;}< <(df .); done)
3.51s

# other solutions

bash-4.2$ time for i in $(seq 500); do df . | tail -1 | cut -f 1 -d " "; done > /dev/null
1.405

bash-4.2$ time for i in $(seq 500); do df . | sed '2!d' | awk '{print $1}'; done > /dev/null
5.407

bash-4.2$ time for i in $(seq 500); do df . | sed -n '2{s/ .*$//;p}'; done > /dev/null
1.767

bash-4.2$ time for i in $(seq 500); do df . | sed '2!d' | awk '{print $1}'; done > /dev/null
3.334

bash-4.2$ time for i in $(seq 500); do df . | gawk 'NR==2{print $1}'; done > /dev/null
3.013

bash-4.2$ time for i in $(seq 500); do df . | mawk 'NR==2{print $1}'; done > /dev/null
1.747

bash-4.2$ time for i in $(seq 500); do df . | perl -nae 'print$F[0]if$.==2'; done > /dev/null
2.752

(Not compared with the stat solution as it not works here.)

share|improve this answer
    
All good; I love that there's so many ways to do this, and it's interesting to see how the time changes between awk, mawk, and gawk. What a great reference for this, thanks for taking the time to benchmark them too! –  antonyh Feb 23 '13 at 22:08
add comment

It's the usual way on UNIX to concatenate the powers of simple programs that to just a little. Hence don't worry to pipe the output of df through some filter.

df /path/to/file | sed -n '2{s/ .*$//;p}'

-n suppresses printing lines automatically, 2{} executes the enclosed commands on second line, s/ .*$// discards everything from the first space, p prints what's left. Adding q after the p in cases when one parses longer input and just wants the second (or n-th) line could speed it up a bit too.

share|improve this answer
    
This didn't work for me: sed: 1: "2{s/ .*$//;p}": extra characters at the end of p command –  antonyh Feb 22 '13 at 13:14
1  
Unix seds are picky on missing ;s. Try to add one after the p command. If still not works, specify what kind of sed are you using. –  manatwork Feb 22 '13 at 13:25
    
BSD sed, as found on OSX 10.7 - df . | sed -n '2{s/ .*$//;p;}' - this works –  antonyh Feb 22 '13 at 13:28
    
This seems to be faster than tail | cut and sed|awk. –  jofel Feb 22 '13 at 13:34
    
Quite right, here's my timings; time for i in {1..500}; do df . | sed -n '2{s/ .*$//;p;}'; done real 0m1.587s user 0m0.472s sys 0m1.180s time for i in {1..500}; do df . | sed '2!d' | awk '{print $1}'; done real 0m2.484s user 0m0.722s sys 0m1.833s time for i in {1..500}; do df . | tail -1 | awk '{print $1}'; done real 0m2.267s user 0m0.672s sys 0m1.701s I just need to work out how to do this one more way then I'll be happy. awk? –  antonyh Feb 22 '13 at 14:49
show 2 more comments

You can use simple one-line with sed, awk as

df . | sed '2!d' | awk '{print $1}'

In sed, specifying 2d mean delete the 2nd line. Adding a ! negate this, so it just deletes all other lines, and prints the 2nd line. The awk command then displays the first column value.

Output:

/dev/disk0s2
share|improve this answer
    
Why both sed and awk? Either is enough. –  manatwork Feb 22 '13 at 12:42
    
Nothing specific. I looked at it as more readable myself :) –  mtk Feb 22 '13 at 12:43
    
what do the options on sed mean? I can guess that 2 means second line, but the rest is cryptic to me. –  antonyh Feb 22 '13 at 13:17
1  
"2!" apply to everything except the second line: "d": delete –  jofel Feb 22 '13 at 13:27
1  
@antonyh: df . | awk 'NR==2{print $1}' –  manatwork Feb 22 '13 at 14:42
show 2 more comments

Parsing the output of df is the best you can do portably. Pass -P to df to avoid it formatting the output in a weird way (you're probably safe everywhere since you're grabbing the first field, but you do need -P to grab the mount point as it may be relegated to a subsequent line if preceding columns are too wide).

device_name=$(df -P . | awk 'NR==2 {print $1}')

Note that some systems allow device names to contain whitespace (IIRC that tends to happen on OSX). There's no portable or convenient way to handle this case.

I don't think there's a better way to do this under Linux. stat can give you the device number (stat -c %t .), but if you want a device entry under /dev, you have to extract it from /proc, which df is better at doing.

share|improve this answer
    
On OSX the -P option forces 512-byte blocks and has nothing to do with formatting. stat -f %Sdr . gives me the device name without the /dev/ prefix so it's close but I don't want to make assumptions that the device isn't in a sub-directory. –  antonyh Feb 23 '13 at 22:03
    
@antonyh -P is for POSIX compatibility. This includes both using 512-byte blocks and not splitting lines. The output of stat -f %Sdr is relative to /dev, you can safely use that on *BSD/OSX. –  Gilles Feb 23 '13 at 23:36
    
my apologies; this isn't mentioned in the man page, only the 512-byteness of the output for -P is in the documentation. –  antonyh Feb 24 '13 at 23:42
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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