15

The following, very non-robust shell code will give the mount point of $path:

 (for i in $(df|cut -c 63-99); do case $path in $i*) echo $i;; esac; done) | tail -n 1

Is there a better way to do this in shell?

Postscript

This script is really awful, but has the redeeming quality that it Works On My Systems. Note that several mount points may be prefixes of $path.

Examples On a Linux system:

cas@txtproof:~$ path=/sys/block/hda1
cas@txtproof:~$ for i in $(df -a|cut -c 57-99); do case $path in $i*) echo $i;; esac; done| tail -1
/sys

On a Mac OSX system

cas local$ path=/dev/fd/0
cas local$ for i in $(df -a|cut -c 63-99); do case $path in $i*) echo $i;; esac; done| tail -1
/dev

Note the need to vary cut's parameters, because of the way df's output differs; using awk solves this, but even awk is non-portable, given the range of result formatting various implementations of df return.

Answer It looks like munging tabular output is the only way within the shell, but

df -P "$path"  | tail -1 | awk '{ print $NF}'

based on ghostdog74's answer, is a big improvement on what I had. Note two new issues: firstly, df $path insists that $path names an existing file, the script I had above doesn't care; secondly, there are no worries about dereferencing symlinks. This doesn't work if you have mount points with spaces in them, which occurs if one has removable media with spaces in their volume names.

It's not difficult to write Python code to do the job properly.

Charles Stewart
  • 11,661
  • 4
  • 46
  • 85

12 Answers12

24

df takes the path as parameter, so something like this should be fairly robust;

df "$path" | tail -1 | awk '{ print $6 }'
JesperE
  • 63,317
  • 21
  • 138
  • 197
  • 1
    As ndim said, this doesn't work for me because of wrapped lines in df output caused by LVM names. The quota command has a --no-wrap option but df doesn't seem to have one, too. – ygoe Sep 19 '10 at 11:29
  • 1
    df has the -P option to not wrap. Newer versions of GNU df do not wrap at all. – pixelbeat Aug 01 '12 at 10:58
  • 1
    It's worth documenting that this doesn't work for mount points with spaces in their path. I've accepted this answer because it is the best of the answers so far given and I have based my preferred solution on it. – Charles Stewart Jun 29 '16 at 10:03
19

In theory stat will tell you the device the file is on, and there should be some way of mapping the device to a mount point.

For example, on linux, this should work:

stat -c '%m' $path
jelle foks
  • 751
  • 6
  • 8
Douglas Leeder
  • 52,368
  • 9
  • 94
  • 137
  • I like this suggestion, but on mac os 10.4, running `stat -f"%Sdr"` returns `???r` for most filesystems. – Charles Stewart Jan 30 '10 at 20:56
  • I've posted a qn about the macosx behaviour to SU: http://superuser.com/questions/103755/whats-up-with-stat-on-macos-darwin-or-filesystems-without-names – Charles Stewart Feb 02 '10 at 10:42
  • 2
    Great! This works if the device is mounted on a path with spaces, while the accepted answer fails. – mavroprovato Apr 26 '14 at 22:18
  • @mavroprovato - This is definitely the way to go if you are on Linux. Unfortunately, the BSDs, including OSX/Darwin, have a completely different syntax for stat format strings. – Charles Stewart Dec 12 '19 at 08:21
  • If Homebrew is an option then GNU stat is available via [coreutils](https://formulae.brew.sh/formula/coreutils) on macOS. – Stefan Schmidt Jun 08 '23 at 02:43
8

Always been a fan of using formatting options of a program, as it can be more robust than manipulating output (eg if the mount point has spaces). GNU df allows the following:

df --output=target "$path" | tail -1

Unfortunately there is no option I can see to prevent the printing of a header, so the tail is still required.

Graeme
  • 2,971
  • 21
  • 26
1

i don't know what your desired output is, therefore this is a guess

#!/bin/bash

path=/home
df | awk -v path="$path" 'NR>1 && $NF~path{
 print $NF
}'

Using cut with -c is not really reliable, since the output of df will be different , say a 5% can change to 10% and you will miss some characters. Since the mount point is always at the back, you can use fields and field delimiters. In the above, $NF is the last column which is the mount point.

ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • Yes, awk is better than cut, but I'd really like to get away from parsing the output of df. Note that mount points are hierarchically organised. – Charles Stewart Jan 30 '10 at 10:53
  • show what is $path, and show your desired output. give as much information in your question as possible. – ghostdog74 Jan 30 '10 at 11:26
1

I would take the source code to df and find out what it does besides calling stat as Douglas Leeder suggests.

Line-by-line parsing of the df output will cause problems as those lines often look like

/dev/mapper/VOLGROUP00-logical--volume
                      1234567  1000000  200000  90% /path/to/mountpoint

With the added complexity of parsing those kinds of lines as well, probably calling stat and finding the mountpoint is less complex.

ndim
  • 35,870
  • 12
  • 47
  • 57
1

If you want to use only df and awk to find the filesystem device/remote share or a mount point and they include spaces you can cheat by defining the field separator of awk to be a regular expression that matches the format of the numeric sizes used to display total size, used space, available space and capacity percentage. By defining those columns as the field separator you are then left with $1 representing the filesystem device/remote share and $NF representing the mount path.

Take this for example:

[root@testsystem ~] df -P
Filesystem                       1024-blocks        Used Available Capacity Mounted on
192.168.0.200:/NFS WITH SPACES   11695881728 11186577920 509303808      96% /mnt/MOUNT WITH SPACES

If you attempt to parse this with the quick and dirty awk '{print $1}' or awk '{print $NF}' you'll only get a portion of the filesystem/remote share path and mount path and that's no good. Now make awk use the four numeric data columns as the field separator.

[root@testsystem ~] df -P "/mnt/MOUNT WITH SPACES/path/to/file/filename.txt" | \
awk 'BEGIN {FS="[ ]*[0-9]+%?[ ]+"}; NR==2 {print $1}'
192.168.0.200:/NFS WITH SPACES

[root@testsystem ~] df -P "/mnt/MOUNT WITH SPACES/path/to/file/filename.txt" | \
awk 'BEGIN {FS="[ ]*[0-9]+%?[ ]+"}; NR==2 {print $NF}'
/mnt/MOUNT WITH SPACES

Enjoy :-)

Edit: These commands are based on RHEL/CentOS/Fedora but should work on just about any distribution.

crashmaxed
  • 510
  • 3
  • 9
1

Just had the same problem. If some mount point (or the mounted device) is sufficent as in my case You can do:

DEVNO=$(stat -c '%d' /srv/sftp/testconsumer)
MP=$(findmnt -n -f -o TARGET /dev/block/$((DEVNO/2**8)):$((DEVNO&2**8-1)))

(or split the hex DEVNO %D with /dev/block/$((0x${DEVNO:0:${#DEVNO}-2})):$((0x${DEVNO:2:2})))

Alternatively the following loop come in to my mind, out of ideas why I cannot find proper basic command..

TARGETPATH="/srv/sftp/testconsumer"
TARGETPATHTMP=$(readlink -m "$TARGETPATH")
[[ ! -d "$TARGETPATHTMP" ]] && TARGETPATHTMP=$(dirname "$TARGETPATH")
TARGETMOUNT=$(findmnt -d backward -f -n -o TARGET --target "$TARGETPATHTMP")
while [[ -z "$TARGETMOUNT" ]]
do
  TARGETPATHTMP=$(dirname "$TARGETPATHTMP")
  echo "$TARGETPATHTMP"
  TARGETMOUNT=$(findmnt -d backward -f -n -o TARGET --target "$TARGETPATHTMP")
done

This should work always but is much more then I expect for such simple task?

(Edited to use readlink -f to allow for non existing files, -m or -e for readlink could be used instead if more components might not exists or all components must exists.)

EOhm
  • 626
  • 4
  • 11
  • If you are on Linux, then `stat -c '%m' "$SOMEFILE"` should work. The reason I didn't accept Leeder's answer is that this answer isn't portable: BSD systems have a completely different syntax for stat format strings. – Charles Stewart Dec 12 '19 at 08:18
  • @CharlesStewart Maybe yes on recent linux. But not on our default still alive EL6 boxes... – EOhm Dec 12 '19 at 17:16
  • Ah, yes. I think GNU coreutils added the --output switch to df before they added the %m fmt to stat: does `df --output=target "$SOMEFILE"` work on that system? – Charles Stewart Dec 13 '19 at 16:45
  • @CharlesStewart the target isn't available on the systems but stat format %m is. It's just that stat %m does return `?` instead of the mountpoint. But anyway yes it could be done with quite unbreakable df parsing, You are right. But it misses requirement that the target may not exist. Which wasn't my requirement so I missed it in the fail-proof loop. (So if it is exsting You could use something @crashmaxed awk solution, or something similiar with sed or bash regex `[[ $(df -P /srv/sftp/testconsumer|tail -1) =~ ^.*\ +[0-9]+\ +[0-9]+\ +[0-9]+\ +[0-9]+%\ +(/.*)$ ]]; MOUNTPOINT=${BASH_REMATCH[1]}` – EOhm Dec 15 '19 at 11:06
0

I use this:

df -h $path | cut -f 1 -d " " | tail -1
Erik Aronesty
  • 11,620
  • 5
  • 64
  • 44
0

Linux has this, which will avoid problem with spaces:

lsblk -no MOUNTPOINT ${device}

Not sure about BSD land.

  • 1
    This does not answer the question as it requires a device file and will not work with any path. – Graeme Jan 16 '14 at 18:16
0
mount | grep "^$path" | awk '{print $3}'
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • This will only find a mount point from a device file, for which `lsblk` or `findmnt` are much better. – Graeme Jan 16 '14 at 18:25
0
f () { echo $6; }; f $(df -P "$path" | tail -n 1)
0

I missed this when I looked over prior questions: Python: Get Mount Point on Windows or Linux, which says that os.path.ismount(path) tells if path is a mount point.

My preference is for a shell solution, but this looks pretty simple.

Community
  • 1
  • 1
Charles Stewart
  • 11,661
  • 4
  • 46
  • 85