Python: Neofetch selbst basteln

  Actionschnitzel   Lesezeit: 8 Minuten  🗪 8 Kommentare Auf Mastodon ansehen

Eine kleine Inspiration, um mit Python anzufangen.

python: neofetch selbst basteln

Hat euch die traurige Nachricht auch kalt erwischt? Neofetch ist nicht mehr! Man kann es natürlich noch nutzen, und wenn es genauso lange dauert, ein Paket aus den Debian-Repos zu entfernen, wie es aufzunehmen, wird Neofetch uns auch noch eine Weile erhalten bleiben. Ralf hat schon in seinem Artikel bestehende Alternativen erwähnt.

Ich habe das mal zum Anlass genommen, eine billige Kopie in Python zu schreiben. Warum billig? Weil das hier ein kleiner Appetitanreger für Python-Interessierte sein soll, die mit dem Gedanken spielen, diese Programmiersprache zu erlernen. Es soll also einfach sein. Ich beschränke mich hier durch die Auflistung von DEBIAN-PAKETEN logischerweise nur auf Debian und seine Derivate. Die schöne ASCII-Art fällt auch weg. Mich interessieren nur die rohen Daten. Was auch fehlt, sind Desktop-spezifische Daten. Das könnten wir auch einfügen, würde aber das Testen auf mehreren Desktop-Umgebungen voraussetzen und uns direkt in die "IF/ELSE-STATEMENT-HÖLLE" bringen. Da wollt ihr als Anfänger nicht rein, glaubt mir. Auch die Berechnung des RAMs habe ich weggelassen. Das ist zu viel Mathematik.

Den folgenden Code können wir in einer Datei mit dem Namen "fetch_data.py" an einem beliebigen Ort abspeichern. Der Einfachheit halber direkt im HOME. Im Terminal könnt ihr das Ergebnis sehen, indem ihr python3 fetch_data.py eingebt und mit ENTER bestätigt.

import os
import subprocess
import socket
import platform
import psutil

# Get User and Host
user = os.getlogin()
hostname = socket.gethostname()

print(f"{user}&{hostname}")

total_string = len(user) + len(hostname) +1
print("-" * total_string)

# Get Name of Distro
os_release = subprocess.run(["grep", "-E", "^(PRETTY_NAME)=", "/etc/os-release"], stdout=subprocess.PIPE, text=True)
nice_name = os_release.stdout.strip().split('=')[1].strip('"')
print(f"OS: {nice_name}")

# Get Kernel Release
kernel_release = platform.release()
print(f"Kernel: {kernel_release}")

# Get installed Debian Packages
deb_count = subprocess.run(["dpkg", "--list"], stdout=subprocess.PIPE, text=True)
deb_counted = len(deb_count.stdout.splitlines()) - 5
print(f"Packages: {deb_counted} (Debian)")

# Get used Shell
get_shell = os.environ["SHELL"]
print(f"Shell: {get_shell}")

# Get Screen Resolution
get_resolution = subprocess.run(["xrandr"], stdout=subprocess.PIPE, text=True)
resolution_lines = get_resolution.stdout.splitlines()
resolution = [line.split()[0] for line in resolution_lines if "*" in line]
print(f"Resolution: {' '.join(resolution)}")

# Get Max CPU Frequency
cpufreq = psutil.cpu_freq()
print(f"CPU: {cpufreq.max:.0f} Mhz")

Um es wirklich einfach zu halten, habe ich mich überwiegend auf Module beschränkt, die in Python enthalten sind. Je nach Derivat kann es sein, dass man python3-psutil nachinstallieren muss. Was folgt, ist eine nette Auswahl an Mechaniken, um das System auszulesen. Eine kleine Spielerei findet ihr in total_string. Hier habe ich mit len die Buchstaben von user und hostname zusammengezählt, damit die gestrichelte Linie immer die richtige Länge hat. Falls ihr Interesse habt, Python zu lernen, kann ich euch, ohne große Werbung zu machen, das Buch Python 3 Das umfassende Handbuch von Rheinwerk empfehlen.

Hier noch einmal eine etwas genauere Auflistung der Geschehnisse:


  1. Benutzername und Hostname abrufen:

    • user = os.getlogin(): Ruft den Benutzernamen des aktuellen Benutzers ab.
    • hostname = socket.gethostname(): Ruft den Hostnamen des Systems ab.
    • print(f"{user}&{hostname}"): Druckt den Benutzernamen und den Hostnamen.
  2. Zeichenfolge für Trennlinie erstellen:

    • total_string = len(user) + len(hostname): Berechnet die Gesamtlänge der Zeichenfolge aus Benutzername und Hostname.
    • print("-" * total_string): Druckt eine Zeichenfolge von Bindestrichen mit der berechneten Länge aus, um eine Trennlinie zu erstellen.
  3. Name des Betriebssystems abrufen:

    • os_release = subprocess.run(["grep", "-E", "^(PRETTY_NAME)=", "/etc/os-release"], stdout=subprocess.PIPE, text=True): Führt den Befehl grep aus, um die Zeile aus der Datei /etc/os-release zu finden, die mit "PRETTY_NAME" beginnt. Das Ergebnis wird über subprocess.PIPE abgerufen.
    • nice_name = os_release.stdout.strip().split('=')[1].strip('"'): Verarbeitet den Ausgabestring, um den Namen des Betriebssystems zu extrahieren.
  4. Kernelversion abrufen:

    • kernel_release = platform.release(): Ruft die Kernelversion des Betriebssystems ab.
  5. Anzahl der installierten Debian-Pakete abrufen:

    • deb_count = subprocess.run(["dpkg", "--list"], stdout=subprocess.PIPE, text=True): Führt den Befehl dpkg --list aus, um die Liste der installierten Pakete abzurufen. Das Ergebnis wird über subprocess.PIPE abgerufen.
    • deb_counted = len(deb_count.stdout.splitlines()) - 5: Zählt die Anzahl der Zeilen in der Ausgabe und subtrahiert die Header-Zeilen, um die Anzahl der installierten Pakete zu erhalten.
  6. Verwendete Shell abrufen:

    • get_shell = os.environ["SHELL"]: Ruft den Pfad zur verwendeten Shell aus der Umgebungsvariable SHELL ab.
  7. Bildschirmauflösung abrufen:

    • get_resolution = subprocess.run(["xrandr"], stdout=subprocess.PIPE, text=True): Führt den Befehl xrandr aus, um Informationen zur Bildschirmauflösung abzurufen. Das Ergebnis wird über subprocess.PIPE abgerufen.
    • resolution_lines = get_resolution.stdout.splitlines(): Teilt die Ausgabe in Zeilen auf.
    • resolution = [line.split()[0] for line in resolution_lines if "*" in line]: Durchsucht die Zeilen nach denen, die ein Sternchen enthalten, und extrahiert den ersten Wert, der normalerweise den Namen des Ausgabegeräts enthält.
  8. Maximale CPU-Frequenz abrufen:

    • cpufreq = psutil.cpu_freq(): Ruft Informationen zur CPU-Frequenz mit der psutil-Bibliothek ab.

Quellen:
https://stackoverflow.com/questions/3103178/how-to-get-the-system-info-with-python
https://thepythoncode.com/article/get-hardware-system-information-python
https://www.askpython.com/python/examples/retrieve-screen-resolution-in-python
https://www.baeldung.com/linux/screen-resolution
https://s3.dualstack.us-east-2.amazonaws.com/pythondotorg-assets/media/community/logos/python-logo-only.png

Tags

Python, System-Daten, Neofetch

kamome
Geschrieben von kamome am 6. Mai 2024 um 11:32

Nette Idee! Die Anzahl der wirklich installierten Pakete (mit dem Vorteil, dass die Header-Zeilen wegbleiben):

dpkg -l |grep -c '^i'

Noch sicherer ohne Header (wenn aptitude installiert):

aptitude search '~i'
Claudia
Geschrieben von Claudia am 6. Mai 2024 um 17:56

Ich habe es ohne python-psutil gemacht, weil die Linux Befehle alle schon vorhanden sind. Somit habe ich es mir etwas sehr leicht gemacht. Jedenfalls im nachfolgenden ist mein Code Beispiel für ein kleines NeoFetchPythonSkript:

#!/usr/bin/env python3

from os import getlogin as osGetlogin, environ as osEnviron
from platform import release as platformRelease
from re import split as reSplit
from socket import gethostname as socketGethostname
import subprocess

def runSubProcess( arrAction ):
  result = subprocess.run( arrAction, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True )
  if result.returncode == 0:
    return result.stdout.strip()
  return result.stderr.strip()

def runPipedCommands( *commands ):
  process = subprocess.Popen( commands[0], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True )

  for cmd in commands[1:]:
    nextProcess = subprocess.Popen( cmd, stdin=process.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    process.stdout.close()
    process = nextProcess

  result, err = process.communicate()
  returnCode = process.returncode
  process.stdout.close()
  if returnCode == 0:
    return result.strip()
  return err.strip()

def prettyLine( intStrLength ):
  print("-" * (intStrLength + 1))

def getContentBetweenQuotes( strWithQuotes ):
  return reSplit('"(.*)"', strWithQuotes)[1].strip()

user = osGetlogin()
hostname = socketGethostname()
osName = runSubProcess( ["grep", "-E", "^(NAME)=", "/etc/os-release"] )
osRelease = runSubProcess( ["grep", "-E", "^(VERSION)=", "/etc/os-release"] )
debPkgCount = runPipedCommands( ["dpkg", "-l"], ["grep", "^i"], ["wc", "-l"] )
displayResoulution = runPipedCommands( ["xrandr"], ["grep", "*"], ["awk", "{print $1}"] )
cpuArch = runPipedCommands( ["lscpu"], ["head", "-1"], ["awk", "{print $2}"] )
cpuModel = runPipedCommands( ["lscpu"], ["grep", "-i", "Modellname:"], ["cut", "-sd", ":", "-f2"], ["tr", "-s", " "] )
gpuModel = runPipedCommands( ["lshw", "-C", "display"], ["grep", "-i", "produ"], ["cut", "-sd", ":", "-f2"], ["head", "-1"] )
usedTerminal = runPipedCommands( ["ps", "a"], ["grep", "terminal"], ["head", "-1"], ["awk", "{print $5}"] )
uptime = runPipedCommands( ["uptime"], ["cut", "-sd", ",", "-f1"], ["awk", "-F", "up", "{print $2}"] )
hours, minutes = uptime.strip().split(":")
memory = runPipedCommands( ["free", "-m"], ["head", "-2"], ["tail", "-1"], ["awk", "{print $2,$7}"] )
totalMemory, usedMemory = memory.strip().split(" ")

print(f"{user}@{hostname}")
prettyLine(len(user)+len(hostname))
print("OS-Name:", getContentBetweenQuotes(osName))
print("OS-Version:", getContentBetweenQuotes(osRelease))
print(f"Kernel:",platformRelease())
print(f"Packages: {debPkgCount} (dpkg)")
print(f"Shell: {osEnviron['SHELL']}")
print(f"Resolution: {displayResoulution}")
print(f"CPU: {cpuModel} ({cpuArch})")
print(f"GPU: {gpuModel}")
print(f"Terminal: {usedTerminal}")
print(f"Uptime: {hours} hour(s) {minutes} min")
print(f"Memory: {usedMemory} / {totalMemory} MiB")
Claudia
Geschrieben von Claudia am 6. Mai 2024 um 18:58

Ich weiß nicht warum jetzt die Anführungszeichen codiert wurden. Aber für alle, die es nicht wissen. Mit einen beliebigen Texteditor alle '\&quot\;' ersetzen mit 'double quotes' zu deutsch 'Anführungsstriche'. Auf der deutschentastatur die 2 mit der Shift-Taste.

Actionschnitzel
Geschrieben von Actionschnitzel am 6. Mai 2024 um 23:08

Dein Code crasht, weil uptime.strip() Stunden und Minuten erwartet. In meinem Fall ist der PC 20 Minuten. Der Output ist "20 Min", nicht 0 Stunden 20 Minuten

Claudia
Geschrieben von Claudia am 7. Mai 2024 um 09:49

Daran habe ich nicht gedacht, wenn der Rechner noch keine Stunde läuft. Mein Fehler und hier die Lösung:

# Variante 01
if ':' in uptime:
  hours, minutes = uptime.strip().split(':')
else:
  hours = 0
  minutes, _ = uptime.strip().split(' ')

Eine saubere Lösung wäre dies hier:

# Variante 02
if ':' not in uptime:
  uptime = '0:' + uptime

Jeweils vordem python-code hours, minutes = uptime.strip().... setzen Variante 01 oder Variante 02. Danach sollte es auch bei Dir funktionieren @kamome.

kamome
Geschrieben von kamome am 8. Mai 2024 um 09:09

Danke @Claudia, allerdings kam das von Actionschnitzel, nicht von mir ;)

Claudia
Geschrieben von Claudia am 7. Mai 2024 um 15:23

Ich hätte noch eine dritte Möglichkeit in peto, somit würde es ein zwei Zeiler bleiben:

uptime = runPipedCommands( ['uptime'], ['cut', '-sd', ',', '-f1'], ['tr', '-s', ' '], ['awk', '-F', 'up ', '''{if ($2 ~ /:/){print $2} else { gsub(/ min/," ",$2); print "0:"$2 }}'''] )
hours, minutes = uptime.strip().split(':')

Erklärung für den awk-commando:

uptime | cut -sd ',' -f1 | tr -s ' ' | awk -F'up ' '{if ($2 ~ /:/){print $2} else { gsub(/ min/,"",$2); print "0:"$2 }}'

# Seperator mit einem Leerzeichen: -F'up '
# IF Bedingung: Enthält der String einen ':', dann gebe nur die rechte Seite von 'up ' aus
# ELSE Teil: Ansonsten gebe "0:" aus bevor der rechte Teil ausgegeben wird vom 'up '.

Ich hoffe, dass dies als Erklärung reicht, ansonsten einfach mal die Manual-Page öffnen von awk-commando: man awk.

P.S.: Ich bitte um Entschuldigung, dass dies nicht gleich alles in der ersten Antwort enthalten war. Diese Entschuldigung geht an alle Leser und Leserinnen und den Verwaltern und Verwalterinnen, die meine Antwort ständig freigeben müssen.

Mein kleiner Tick, wenn es nicht funktioniert. Dann muss ich solange herumbasteln bis es funktioniert.

oliverpk
Geschrieben von oliverpk am 10. Mai 2024 um 21:52

Cool. Habe die selbe Idee in rust implementiert, auch wenn manche Infos noch fehlen: https://github.com/oliverpk2000/rust-fetch