0

With Macbook 2021 (arm64).

uname -a
Darwin MacBook.local 21.1.0 Darwin Kernel Version 21.1.0: Wed Oct 13 17:33:01 PDT 2021; root:xnu-8019.41.5~1/RELEASE_ARM64_T6000 arm64

External drive, SSD2TB, is NTFS.

diskutil info disk4
   Device Identifier:         disk4
   Device Node:               /dev/disk4
   Whole:                     Yes
   Part of Whole:             disk4
   Device / Media Name:       External

   Volume Name:               SSD2TB
   Mounted:                   Yes
   Mount Point:               /Volumes/SSD2TB

   Content (IOContent):       None
   File System Personality:   NTFS
   Type (Bundle):             ntfs
   Name (User Visible):       Windows NT File System (NTFS)

The simplest of tests, is to simply report a directory name for each cycle, e.g.

python3
Python 3.10.1 (main, Dec 31 2021, 10:22:35) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import os, os.path
>>> os.chdir("/Volumes/SSD2TB/Photos")
>>> from glob import glob
>>> glob('*')
['Pictures']
>>> for d,dd,f in os.walk('.'): print(f"{dd}")
... 
[]
>>> os.path.isdir('Pictures')
True
>>> for d, dd, f in os.walk('.'):
...     print(f"{f}")
... 
['.DS_Store', 'Pictures']
>>> for d, dd, f in os.walk('.'):
...     print(f"{d}")
... 
.

Does anyone understand why the sub-directory in '.' is getting reported as a file in os.walk (by getting returned in the 'f' variable? And the 'dd' variable which should be the list of directories returns an empty list.

One final note. If I try the same test in a path on the local drive, then everything behaves as expected. Directories are reported in variable 'dd' and files in 'f'.

Using pathlib2's iterdir() with 'is_dir()' will generate a list of sub-dirs and files, e.g.

dd = [x for x in Path(".").iterdir() if x.is_dir()]
f  = [x for x in Path(".").iterdir() if x.is_file()]

This works with the external ntfs drive, so os.walk is missing something with macos and the default mount of ntfs.

2
  • it kind of makes sense that the os library might have difficulties when mixing and matching components from different OS's. its also likely that the issue is in the Mac file system abstraction, or in their NTFS drivers. Python doesn't likely try to support filesystems themselves, but instead use the OS to present directory structures in a uniform manner. Commented Jan 24, 2022 at 21:19
  • @Frank, Yes, that's probably true. The next step is to try a 'proper' ntfs handler on macos like ntfs-3g or similar, and try it again.
    – John Tweed
    Commented Jan 25, 2022 at 7:50

2 Answers 2

0

I've now installed a trial version of 'NTFS for MAC' by Paragon software, and after jumping through a few security hoops, my external ntfs drive is mounted (R/W).

for d, dd, f in os.walk("."):
    if re.match(r'.*?/\.@.*', d):
        continue
    print(f"{d}")

Now returns (as originally expected), a tree of directories from '.'.

E.g.

.
./Pictures
./Pictures/1998
./Pictures/1998/Barcelona
./Pictures/1998/Barcelona/.comments
./Pictures/2000

Installing ntfs-3g may also work but for my Macbook 2021 (arm64) with OS 'Monterey', the installation process is a bit more involved.

At least there is a solution. But after 10 days, it will be $19. Perhaps if I have an ongoing need to use NTFS drives this way it will be worth the cost vs struggling to install ntfs-3g.

So the real culprit lies with the way Apple have chosen to present a read only NTFS drive and the way Python (os.walk) handles it.

BTW... I suspect that the 'Strawberry' music player was also refusing to scan a music folder on the external NTFS drive for the same reasons. It would just return a blank library.

0

Not strictly an answer but an alternative solution. As I said in the original question, pathlib's Path.iterdir(), does function with the original Apple ntfs mount.

So building on that, here's an alternative to os.walk, that, (upto the recursion limit), does work as os.walk should.

import os, os.path, sys
from pathlib2 import Path

def pwalk(d):
    d = Path(d).resolve()
    dd = [ x for x in d.iterdir() if x.is_dir()]
    f  = [ x for x in d.iterdir() if x.is_file()]
    yield (d, dd, f)
    for xd in dd:
        yield from pwalk( d / xd)

So, using the same case above,

for d, dd, f in pwalk('.'): print(f"{d}")
.
./Pictures
./Pictures/1998
./Pictures/1998/Barcelona
./Pictures/1998/Barcelona/.comments
./Pictures/2000

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .