How to find what device a file is on (and use that in a script)?

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?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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.)

Method 2

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.

Method 3

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

Method 4

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.

Method 5

If you’re using linux, you can do it with findmnt (part of util-linux package) “without resorting to string hacking”:

findmnt -no source -T /path/to/file
/dev/sda1

When using the option

-T, --target path

if the path is not a mountpoint file or directory, findmnt checks path elements in reverse order to get the mountpoint. The other two options suppress the header line: -n, --noheading and select the column(s) to be listed: -o, --output


df from coreutils has a similar option --output= to print only certain fields, like source e.g.:

df --output=source /path/to/file
Filesystem
/dev/sda1

there’s no option to remove the header though. If that is a problem you’ll have to do some minimal string hacking e.g. pipe it to sed 1d

Method 6

Note that none of the above solutions will work, if you are using ecryptfs.

However, the solution is simple: Just re-call the commands with the output as an argument, i.e.:

  • findmnt -no source -T "$(findmnt -no source -T /path/to/file)"
  • df "$(df . | awk 'NR==2{print $1}')" | awk 'NR==2{print $1}'

Unfortunately df . | sed '2!d' | awk '{print $1}' will not work at all.

$ df "$(df . | sed '2!d' | awk '{print $1}')" | sed '2!d' | awk '{print $1}'
bash: !d': event not found

First, bash will try to interpret ! inside double-quotes as a history expansion (doesn’t matter that inside command substitution it’s inside single quotes), then escaping it …

$  df "$(df . | sed '2!d' | awk '{print $1}')" | sed '2!d' | awk '{print $1}'
sed: -e expression #1, char 2: unknown command: `'

… will make sed complain about it (because is inside single quotes inside the command substitution, so it’s literal and not replaced with simply !)

You could still manually do it, though.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x