Created
March 30, 2025 16:08
-
-
Save Hammer2900/e25989ffe7e5cbf5259a62fb96e822f9 to your computer and use it in GitHub Desktop.
find wi-fi in linux pc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import subprocess | |
import shlex | |
import re # Import regular expressions module for parsing | |
from typing import Dict, List, Union, Any # Updated type hints | |
def get_wifi_networks_sorted() -> List[Dict[str, Union[str, int]]]: | |
""" | |
Retrieves available Wi-Fi networks including BSSID and signal strength, | |
sorted by signal strength (descending). | |
Uses 'nmcli' to fetch SSID, SECURITY, BSSID, and SIGNAL for each detected | |
access point. Parses the tabular output, converts signal strength to an integer, | |
and returns a list of dictionaries, sorted from strongest signal to weakest. | |
Returns: | |
List[Dict[str, Union[str, int]]]: A list of dictionaries, each representing | |
an access point. Each dictionary contains keys: 'ssid' (str), | |
'security' (str), 'bssid' (str), and 'signal' (int). | |
Returns an empty list if 'nmcli' fails, is not found, | |
no networks are detected, or parsing fails. | |
Raises: | |
FileNotFoundError: If the 'nmcli' command cannot be found. (Handled internally). | |
subprocess.CalledProcessError: If 'nmcli' returns an error. (Handled internally). | |
""" | |
# Request specific fields using -f for field mode. Tabular output is easier to parse reliably | |
# when BSSID (containing colons) is involved, compared to --get-values with multiple fields. | |
command = ["nmcli", "-f", "SSID,SECURITY,BSSID,SIGNAL", "device", "wifi", "list", "--rescan", "no"] | |
# '--rescan no' avoids potential delays. | |
networks_list: List[Dict[str, Union[str, int]]] = [] | |
try: | |
result = subprocess.run( | |
command, | |
capture_output=True, | |
text=True, | |
check=True, | |
encoding='utf-8', | |
errors='replace' | |
) | |
output_lines = result.stdout.strip().splitlines() | |
except FileNotFoundError: | |
print(f"Error: '{command[0]}' command not found. Is NetworkManager installed and in the PATH?") | |
return [] | |
except subprocess.CalledProcessError as e: | |
if e.returncode == 10 and ("device found" in e.stderr.lower() or "wifi is disabled" in e.stderr.lower()): | |
print("Info: No Wi-Fi device found or Wi-Fi is disabled.") | |
return [] | |
print(f"Error executing nmcli command:") | |
try: | |
print(f" Command: {' '.join(map(shlex.quote, command))}") | |
except AttributeError: | |
print(f" Command: {' '.join(command)}") | |
print(f" Return Code: {e.returncode}") | |
print(f" Stderr: {e.stderr.strip()}") | |
return [] | |
except Exception as e: | |
print(f"An unexpected error occurred: {e}") | |
return [] | |
if not output_lines or len(output_lines) < 2: # Check if there's output beyond a potential header | |
print("Info: No network data returned by nmcli.") | |
return [] | |
# Define regex to capture BSSID and Signal at the end of the line, | |
# being robust against spaces in SSIDs. | |
# (.+?) : SSID (non-greedy match) | |
# \s+ : Separating whitespace | |
# (\S+) : Security type (non-whitespace characters) - handles WPA/WPA2/WPA3/-- etc. | |
# \s+ : Separating whitespace | |
# ([0-9A-Fa-f:]{17}) : BSSID (exactly 17 hex chars and colons) | |
# \s+ : Separating whitespace | |
# (\d{1,3}) : Signal (1 to 3 digits) | |
# $ : End of the line | |
# Increased robustness: Account for potentially missing security field or hidden SSIDs ('--'). | |
# Regex now looks for BSSID and SIGNAL specifically, then works backwards. | |
# Updated regex groups: 1=SSID+Security, 2=BSSID, 3=SIGNAL | |
line_regex = re.compile(r'^(.*?)\s+([0-9A-Fa-f:]{17})\s+(\d{1,3})$') | |
# Find separation between SSID and Security in the first group later. | |
header_line = output_lines[0].upper() # Use upper case for case-insensitive header check | |
data_lines = output_lines[1:] | |
# Basic validation: Check if header seems correct | |
if not ("SSID" in header_line and "BSSID" in header_line and "SIGNAL" in header_line): | |
print(f"Warning: Unexpected nmcli output header: {output_lines[0]}") | |
# Attempt to parse anyway, but it might fail | |
for line in data_lines: | |
line = line.strip() | |
if not line: | |
continue | |
match = line_regex.match(line) | |
if match: | |
ssid_security_part = match.group(1).strip() | |
bssid = match.group(2) | |
try: | |
signal = int(match.group(3)) | |
except ValueError: | |
print(f"Warning: Could not parse signal for line: {line}") | |
continue # Skip this line if signal isn't an integer | |
# Now split the first part (SSID + Security) | |
# Find the last block of non-space characters - that should be the security type. | |
last_space_index = ssid_security_part.rfind(' ') | |
if last_space_index == -1: | |
# Assume no space means it might be just an SSID or just Security (less likely) | |
# This case needs careful handling - is it a hidden SSID with Security, or SSID with no Security? | |
# Based on nmcli format, it's likely an SSID without specified security or hidden "--" SSID | |
if ssid_security_part == '--': # Treat '--' as Hidden SSID marker by nmcli | |
ssid = "--" # Or "Hidden SSID" | |
security = "Open" # Assume Open if security field is implicitly missing/merged | |
else: | |
ssid = ssid_security_part | |
security = "Open" # Assumption: if no space/security detected, it's open | |
else: | |
ssid = ssid_security_part[:last_space_index].strip() | |
security = ssid_security_part[last_space_index:].strip() | |
if security == '--': | |
security = 'Open' # Standardize '--' security to 'Open' | |
# Handle empty SSID field explicitly marked by nmcli | |
if not ssid or ssid == '--': | |
ssid = "--" # Or could use "(Hidden SSID)" | |
networks_list.append({ | |
'ssid': ssid, | |
'security': security, | |
'bssid': bssid, | |
'signal': signal | |
}) | |
else: | |
print(f"Warning: Could not parse line: {line}") | |
# Sort the list of dictionaries by 'signal' in descending order | |
# Use a lambda function as the key for sorting | |
networks_list.sort(key=lambda x: x['signal'], reverse=True) | |
return networks_list | |
if __name__ == "__main__": | |
wifi_data = get_wifi_networks_sorted() | |
if wifi_data: | |
print("\nAvailable Wi-Fi Networks (Sorted by Signal Strength):") | |
print("-" * 70) # Separator line | |
# Calculate maximum lengths for neat formatting | |
# Provide default minimum width for columns like BSSID | |
max_ssid_len = max(len(net['ssid']) for net in wifi_data) if wifi_data else 10 | |
max_security_len = max(len(net['security']) for net in wifi_data) if wifi_data else 8 | |
bssid_len = 17 # Fixed length | |
signal_len = 6 # Room for "Signal" header and 3 digits | |
# Adjust potentially short lengths for headers | |
max_ssid_len = max(max_ssid_len, len("SSID")) | |
max_security_len = max(max_security_len, len("Security")) | |
# Print Header | |
print(f"{'SSID':<{max_ssid_len}} {'Security':<{max_security_len}} {'BSSID':<{bssid_len}} {'Signal':>{signal_len}}") | |
print(f"{'-'*max_ssid_len} {'-'*max_security_len} {'-'*bssid_len} {'-'*signal_len}") | |
# Print Data | |
for network in wifi_data: | |
print(f"{network['ssid']:<{max_ssid_len}} " | |
f"{network['security']:<{max_security_len}} " | |
f"{network['bssid']:<{bssid_len}} " | |
f"{network['signal']:>{signal_len}}") # Right-align signal strength | |
else: | |
# Message already printed by the function in case of error/no networks | |
print("\nNo Wi-Fi network information to display.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment