Python Forum
A better way to search the contents of a directory?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
A better way to search the contents of a directory?
#1
I want to find out which ttyUSB port has been assigned to a specific device.

I know the name of the device, it is a generic "Prolific" USB to Serial adapter cable. Over the past few days, I have noticed that it is not always on the same port: The first time I looked, it was on ttyUSB4 and today it is on ttyUSB0.

So, I wrote a short, simple script with the aim of reading the file names of the symlinks in /dev/serial/by-id/, finding the one that has "Prolific" in and reporting back to me the ttyUSB number.

The way I have done it, is that the script runs the bash command to search the directory and save the results to a temporary file. It then reads the file, finds the line with "Prolific" in and prints out the full ttyUSB node name.

Afterwards, it then deletes the file.

I would like to somehow not create a file, rather hold the contents of the bash search results as a list/tuple/something-or-other...

Here is the script:
import os

def main():

    cmd = 'ls -l /dev/serial/by-id | grep -h "Prolific"> usb_devices.txt'
    os.system(cmd)

    with open('usb_devices.txt') as f1:
        contents = f1.read()
        x = contents.find("tty")
        port = (contents[x:])
        if x == -1:
            print("Not found")
        else:
            print(port)
        f1.close()
    os.remove('usb_devices.txt')

    # Restart? yay or nay?
    restart = input("Do you want to try again? Y/N: ")
    print("")
    if restart.lower() == 'y':
        main()
    else:
        print("Goodbye")

main()
Reply
#2
If I understand correctly, I think a design similar to this would work easily
def directory(path):
    for file in os.listdir(path):
        if "keyword".lower() in file.lower():
            return os.path.join(path, file)
        try:
            return directory(os.path.join(path, file))
        except NotADirectoryError:
            pass

directory(directory_to_search_from)
I think you could also replace the return for a yield to return multiple files which match the keyword.
Reply
#3
Thanks for help. However, I think I have missed something out.

This is how I tried your script:
import os

def directory(path):
    for file in os.listdir(path):
        if "Prolific".lower() in file.lower():
            return os.path.join(path, file)
        try:
            return directory(os.path.join(path, file))
        except NotADirectoryError:
            pass
 
directory('/dev/serial/by-id')
Where the files to search are in /dev/serial/by-id and the keyword is Prolific.

It ran without any errors, but did not print any results.

In my script, running the Bash command ls -l uses the long listing format which also includes where the symlink links to.

I went to the Python docs page for Miscellaneous operating system interfaces, to see if there was anything equivalent to the Bash command switch -l, but there didn't seem to be.

I tried replacing for file in os.listdir(path): with for file in os.scandir(path):, but just got a whole host of errors - though I confess I just replaced that one word and didn't read any more about scandir before hitting run, so I am not surprised it didn't work.

I shall play with it further...
Reply
#4
(Apr-07-2021, 06:55 PM)alloydog Wrote: It then reads the file, finds the line with "Prolific" in and prints out the full ttyUSB node name.

Afterwards, it then deletes the file.

I would like to somehow not create a file, rather hold the contents of the bash search results as a list/tuple/something-or-other...
You should not use os.system() it has been replace bye subprocess .
To not save to a file can capture output and then to a search.
Example:
import subprocess

out = subprocess.run(['ping', 'google.com'], capture_output=True, encoding='utf-8')
print(out.stdout)
Output:
Pinging google.com [172.217.20.46] with 32 bytes of data: Reply from 172.217.20.46: bytes=32 time=39ms TTL=119 Reply from 172.217.20.46: bytes=32 time=39ms TTL=119 Reply from 172.217.20.46: bytes=32 time=46ms TTL=119 Reply from 172.217.20.46: bytes=32 time=53ms TTL=119 Ping statistics for 172.217.20.46: Packets: Sent = 4, Received = 4, Lost = 0 (0% loss), Approximate round trip times in milli-seconds: Minimum = 39ms, Maximum = 53ms, Average = 44ms
So let say i want to find Average value that will change.
I would write it like this.
import subprocess
import re

out = subprocess.run(['ping', 'google.com'], capture_output=True, encoding='utf-8')
search_value = re.search(r"Average = (.*)", out.stdout)
print(f'<Average> found with vaule of {search_value.group(1)}')
Output:
<Average> found with vaule of 33ms
Reply
#5
"directory" tries to return data, but the caller doesn't capture or print anything. So nothing will ever be displayed.

If you know the directory it will be in (and don't need to walk the whole tree), I'd prefer something like this:

import os

def scan_dir_for_targets(path, match):
    results = []
    for entry in os.scandir(path):
        link_target = (entry.is_symlink() and os.readlink(entry.path)) or ""
        if match.lower() in link_target.lower():
            results.append(f"{entry.name} -> {link_target}")
    return results

device_name = "Prolific"
print("\n".join(scan_dir_for_targets('/dev', device_name)))
If you did have to walk the tree, maybe something like:
def walk_dir_for_targets(path, match):
    results = []
    for dirpath, dirs, files in os.walk(path):
        for filename in files:
            filepath = os.path.join(dirpath, filename)
            link_target = (os.path.islink(filepath) and os.readlink(filepath)) or ""
            if match.lower() in link_target.lower():
                results.append(f"{filename} -> {link_target}")
    return results
Reply
#6
Thanks for suggestions and help,folks.

However, before I got back here, I had been playing around with it and realised that I was approaching from the wrong direction. Instead of using the command line, I looked for the equivalent Python functions.

This is what I ended up with:
import os, fnmatch

# Setting the scene
pattern = "*Prolific*"
targetDir = ('/dev/serial/by-id/')
symlinkLocation = os.listdir(targetDir)

# Finding Nemo
for device in symlinkLocation:
    if fnmatch.fnmatch(device, pattern):
        targetDevice = device
        prolificSymlink = os.readlink(targetDir+targetDevice) # Full path to symlink
        targetPort = prolificSymlink.replace('../', '')
        print(targetPort)
And it works just nicely :)
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Best way to download the contents of a web directory. Pedroski55 2 1,349 Feb-11-2022, 12:41 PM
Last Post: Pedroski55
  Cloning a directory and using a .CSV file as a reference to search and replace bg25lam 2 2,101 May-31-2021, 07:00 AM
Last Post: bowlofred
  Merging all file_name.log's files from directory to one and search “PerformanceINFO" sutra 0 1,760 Dec-09-2020, 05:14 PM
Last Post: sutra
  I have an array, how can I search a seperate file for the contents of my array? Mr_Keystrokes 0 2,325 Mar-13-2018, 02:25 PM
Last Post: Mr_Keystrokes
  Copy directory (+all contents) via SSH j.crater 7 10,747 Mar-27-2017, 08:39 AM
Last Post: j.crater

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020