[erledigt] Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

[erledigt] Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 30.12.2017 15:04:57

Edit: Ergänzung

Hallo,

ich bin noch recht neu bei Linux, aber ich habe schon das Schreiben von mehr oder weniger kleinen Scripten als eine schöne kleine Beschäftigung für zwischendurch entdeckt.
Ich habe auch schon einige Sachen verfasst, aber jetzt würde ich euch erstmal gerne mein letztes Projekt zeigen und euch um eure Meinung dazu bitten.
Es geht um einen File Manager, den ich basierend auf dmenu geschrieben habe.
Möchte sich ein erfahrener Scripte-Schreiber mein Script vielleicht mal anschauen und mir als Anfänger sagen, ob das gut so ist? Ich habe mir zwar viel Mühe gegeben und und funktionieren tut es bei mir, aber ich nutze ihn noch nicht so lange und außerdem will ich sichergehen, dass es nicht nur funktioniert, sondern auch sicher funktioniert. Schließlich kann Programmieren und Scripte-Schreiben ja auch eine gefährliche Angelegenheit sein, wenn man nicht genau Bescheid weiß...

Anmerkung: Wer das Script ausprobieren möchte: Bitte nur, wenn Du es vorher genau geprüft hast! Wie gesagt, ich bin Anfänger und kann keine Garantie für die sichere Funktion des Scriptes übernehmen!

So, jetzt aber:
Erstmal eine grobe Beschreibung:
dmenu basiert ja darauf, dass es seinen Input anzeigt und die vom Nutzer getroffene Eingabe nach Output schreibt. Dabei kann der Nutzer entweder einen Menüeintrag auswählen und mit Enter bestätigen, oder etwas in die Suchzeile schreiben und mit Enter abschicken bzw. mit Shift+Enter, wenn ansonsten ein durch die Eingabe ausgewählter Menüeintrag nach Ouput geschrieben wird - s. dazu auch "man dmenu".
Mein file manger startet mit der Ansicht von "LC_ALL="C" ls -1 --group-directories-first -F" des aktuellen Arbeitsverzeichnisses, also üblicherweise $HOME. Nun kann man mit der beschriebenen Methode entweder einen Ordner aus der Liste wählen, also z.B. "Dokumente" oder man kann einen nicht in der Liste enthaltenen Ordner in die Suchleiste schreiben, wie z.B. .config/i3. In jedem Fall macht das Script "cd "Ordner"" und listet dann dessen Inhalte auf.
Durch den manuellen hinzugefügten Menüeintrag "../" kann man jederzeit auch wieder in den Parent-Ordner wechseln.

Wird eine Datei aus der Liste gewählt, wird sie mit dem für den entsprechenden mime-type eingestellen Programm geöffnet. Bei mir also z.B. plain/txt mit nano.
Wird ein grafisches Programm, also nicht nano, sondern z.B libreoffice geöffnet, beendet sich der file manager automatisch, ansonsten bleibt er im Hintergrund geöffnet und steht nach Schließen von nano (z.B.) wieder zur Verfügung.

Gibt man "+" in das Eingabefeld ein (und bestätigt ggf. mit Shift+Enter statt nur mit Enter, wenn ansonsten ein Eintrag aus der Menüliste ausgeführt wird), wechselt der file manager in die erweiterte Ansicht des aktuellen Ordners, also die Ansicht des Ordners, die sich aus "LC_ALL="C" ls -lAh --group-directories-first -F)"" ergibt.
Dort funktioniert alles genauso, wie in der einfachen Ansicht.
Mit "q" kann man die erweiterte Ansicht wieder verlassen.

Ist die Eingabe weder ein Ordner, noch eine Datei, noch "+", noch "q" (im erweiterten Modus), wird sie als Befehl ausgeführt, so z.B. "touch test" oder "rm test". Der file manager bleibt dabei immer geöffnet und aktualisiert seine Ansicht entsprechend.

Beendet wird der file manger mit ESC, dem kill-Signal von dmenu (s. man dmenu).

Hier das Script:

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

set -e

dmenu_cmd () {
	dmenu -i -l 25
}
apps_path="/home/robert/.local/share/applications/"

while true; do
if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
show=''
list="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)"
if [ -d "$list" ]; then
	cd "$list"
elif [ -f "$list" ]; then
	type=$(file -b --mime-type "$list")
	tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@"$apps_path"@" | cut -d ';' -f1)
	if [ ! -r "$tool" ]; then exit; fi
	if [ -n "$(grep 'Terminal=true' "$tool")" ]; then
		urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list""
	else
		exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list" &
		exit
	fi
else
	if [ "$list" = '+' ]; then
		while true; do
		if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
		show=''
		input="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -lAh --group-directories-first -F)" | dmenu_cmd)"
		if [[ "$input" = "4"* ]]; then
			COM=$(echo ${input#4})
			if eval $COM; then
				continue
			else
				show='An error occurred. Please press Enter.'
				continue
			fi
		elif [[ "$input" = 'q' ]]; then
			continue 2
		else
			list2=$(echo ${input##* })
			if [ -d "$list2" ]; then
				cd "$list2"
			elif [ -f "$list2" ]; then
				type=$(file -b --mime-type "$list2")
				tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@"$apps_path"@" | cut -d ';' -f1)
				if [ ! -r "$tool" ]; then exit; fi
				if [ -n "$(grep 'Terminal=true' "$tool")" ]; then
					urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list2""
				else
					exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list2" &
					exit
				fi
			else
				show='An error occurred. Please press Enter.'
			fi
		fi
		done
	else
		if eval $list; then
			continue
		else
			show='An error occurred. Please press Enter.'
			continue
		fi
	fi
fi
done
Zuletzt geändert von RobertDebiannutzer am 05.04.2018 18:33:43, insgesamt 1-mal geändert.

owl102

Re: Frage bzgl. Bash-Script (auf dmenu basierter FM)

Beitrag von owl102 » 30.12.2017 15:58:02

Ich habe jetzt keine Zeit, mir das Script anzuschauen (das hole ich später nach), aber auf jeden Fall würde ich 'mal dein Script durch

Code: Alles auswählen

shellcheck <Dateiname>
jagen, das kann nie schaden.

Wenn du einen Hinweis von shellcheck nicht verstehst: Die sind alle im Wiki von ShellCheck erläutert:

https://github.com/koalaman/shellcheck/wiki

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 30.12.2017 17:20:07

Vielen Dank, das ist ja toll, dieses Programm!
"shellcheck -s bash ~/.fm.sh" (.fm.sh ist der Name von meinem Script) hat mir hauptsächlich Quoting-Probleme ausgegeben, die sich aus einer zu häufigen Nutzung von Anführungszeichen ergaben. So habe ich gelernt, das eine gequotete Variable in einem gequoteten Befehl nicht mehr gequotet ist - das hätte ich nicht gedacht!
Übrig geblieben sind folgende Hinweise:

Code: Alles auswählen

In /home/robert/.fm.sh line 24:
	if [ -n "$(grep 'Terminal=true' "$tool")" ]; then
             ^-- SC2143: Use grep -q instead of comparing output with [ -n .. ].
In /home/robert/.fm.sh line 27:
		exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list" &
                     ^-- SC2046: Quote this to prevent word splitting.
In /home/robert/.fm.sh line 54:
				if [ -n "$(grep 'Terminal=true' "$tool")" ]; then
                                     ^-- SC2143: Use grep -q instead of comparing output with [ -n .. ].
In /home/robert/.fm.sh line 57:
					exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list2" &
                                             ^-- SC2046: Quote this to prevent word splitting.
Hinweise 2 und 4 sind geld eingefärbt, sind aber nicht hilfreich. Das habe ich mit einem pdf getested, welches mit "firejail --apparmor --net=none qpdfview" geöffnet werden sollte. Das klappt aber nur ohne den Verbesserungsvorschlag von shellcheck. Aber warum?
Die Hinweise 1 und 3 sind in der Ausgabe von shellcheck grün gefärbt. Aber die sind doch falsch, denn wenn grep immer "success" als Ergebnis ausgibt, funktioniert ja mein If-Statement nicht mehr, oder wie ist das?

Mein verbessertes Script sieht nun so aus (verbessert habe ich gemäß der Vorschläge von shellcheck ein paar schädliche Anführungszeichen und zwei überflüssige echos):

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

set -e

dmenu_cmd () {
	dmenu -i -l 25
}
apps_path="/home/robert/.local/share/applications/"

while true; do
if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
show=''
list="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)"
if [ -d "$list" ]; then
	cd "$list"
elif [ -f "$list" ]; then
	type=$(file -b --mime-type "$list")
	tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
	if [ ! -r "$tool" ]; then exit; fi
	if [ -n "$(grep 'Terminal=true' "$tool")" ]; then
		urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") $list"
	else
		exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list" &
		exit
	fi
else
	if [ "$list" = '+' ]; then
		while true; do
		if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
		show=''
		input="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -lAh --group-directories-first -F)" | dmenu_cmd)"
		if [[ "$input" = "4"* ]]; then
			COM="${input#4}"
			if eval "$COM"; then
				continue
			else
				show='An error occurred. Please press Enter.'
				continue
			fi
		elif [[ "$input" = 'q' ]]; then
			continue 2
		else
			list2="${input##* }"
			if [ -d "$list2" ]; then
				cd "$list2"
			elif [ -f "$list2" ]; then
				type=$(file -b --mime-type "$list2")
				tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
				if [ ! -r "$tool" ]; then exit; fi
				if [ -n "$(grep 'Terminal=true' "$tool")" ]; then
					urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") $list2"
				else
					exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list2" &
					exit
				fi
			else
				show='An error occurred. Please press Enter.'
			fi
		fi
		done
	else
		if eval "$list"; then
			continue
		else
			show='An error occurred. Please press Enter.'
			continue
		fi
	fi
fi
done

owl102

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von owl102 » 30.12.2017 18:17:05

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
30.12.2017 17:20:07
Das klappt aber nur ohne den Verbesserungsvorschlag von shellcheck.
...da ansonsten Befehl und Argumente als eine Zeichenkette übergeben werden. Das Quoting unterbindet ja das Word Splitting, was man aber hier haben möchte.

Die Möglichkeiten von shellcheck sind halt begrenzt. Es kann nur feststellen, daß etwas nicht gequotet ist, aber nicht, daß man vielleicht genau das benötigt.

In einem meiner Scripte gibt es diese Situation auch:

Code: Alles auswählen

# shellcheck disable=SC2086
tar Cxfz ... $verbosity_options ...
shellcheck meckert hier an, daß $verbosity_options nicht gequotet ist. Da stehen aber mehrere Optionen drin, es darf also nicht gequotet werden, weil ansonsten die ganzen Optionen als eine einzelne (unbekannte) Option an tar übergeben werden würde. (Wäre es hingegen ein Verzeichnisname oder ein Dateiname, sollte es gequotet werden, weil es ansonsten nicht funktionieren würde, wenn ein Leerzeichen enthalten ist.) Aber woher soll shellcheck wissen, was $verbosity_options ist?

Solche Situationen kann man mit "# shellcheck disable=<Warnungsnummer>" angeben, shellcheck weiß dann, daß die nächste Zeile bewußt so geschrieben wurde und unterläßt die angegebene Warnung.
Aber die sind doch falsch, denn wenn grep immer "success" als Ergebnis ausgibt, funktioniert ja mein If-Statement nicht mehr, oder wie ist das?
"grep -q" liefert keine Ausgabe, sondern als Ergebniscode, ob es was gefunden hat oder nicht.

Code: Alles auswählen

if [ -n "$(grep 'Terminal=true' "$tool")" ]; then
sollte sich also identisch verhalten wie

Code: Alles auswählen

if grep -q 'Terminal=true' "$tool"; then
Ansonsten eine Frage: Soll das ein POSIX-Script sein oder ein Bash-Script? Manche möchten lieber kompatibel zu anderen Shells programmieren, manche hingegen haben keine Lust, umständlichen Code zu basteln und verwenden stattdessen bewußt Bash-Syntax. (Ich zum Beispiel gehöre zu letzteren.)

Ich frage, weil dein Code irgendwie ein Mischmasch aus beidem ist; wenn ich Anmerkungen zu deinem Code geben soll, muß ich wissen in welche Richtung.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 30.12.2017 19:06:26

Vielen Dank für Deine Mühe!
Ja, mit grep -q funktioniert es auch, habe es gerade noch einmal separat getestet. Weiß nicht, was ich da vorher gemacht habe...

Bezüglich Deiner Frage ob bash oder POSIX:
Ich weiß nicht recht... Ich habe als Shebang einfach mal #!/bin/bash genommen, weil ich für bash die Manpage habe, wo ich bei Problemen reinschauen kann, und mir es erst einmal einfacher erschien, nicht auf POSIX-Kompatibilität zu achten.

Im Prinzip tendiere ich aber zu Kompatibilität. Könntest mir vielleicht kurz erklären, was sich ändern würde, je nachdem, wie ich mich entscheide?

Ich nehme mal an, wenn es POSIX-kompatibel sein soll, muss die dmenu_cmd-Funktion ersetzt werden und die if-statements müssen überarbeitet werden, richtig?
Wenn es dagegen rein für bash sein soll, würde man vielleicht die vielen if durch case ersetzen?

Bei bash gefallen mir die Möglichkeiten unter dem Kapitel "parameter expansion" in der Manpage sehr gut, das habe ich ja auch schon in meinen file manager eingebaut. Bei Tests mit "time" waren die eingabauten Funktionen auch immer schneller als z.B. sed (wobei sed bei den einfachen Aufgaben, die bei mir zu erledigen sind, schon schneller ist als awk).
Das liegt wahrscheinlich hauptsächlich daran, dass bash ja schon läuft, während sed z.B. erst gestartet werden muss. Insofern ist es wohl immer effizienter, für eine bestimmte shell zu schreiben. Oder ist das Blödsinn bei meinen recht kleinen Aufgaben, weil man den Zeitgewinn von 0,002s oder so eh nicht merkt?

owl102

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von owl102 » 30.12.2017 19:25:21

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
30.12.2017 19:06:26
Könntest mir vielleicht kurz erklären, was sich ändern würde, je nachdem, wie ich mich entscheide?
Du müsstest dich entweder auf POSIX beschränken oder könntest auch die vielen Sachen benutzen, die die bash zusätzlich zum POSIX-Standard anbietet.

(Ich persönlich benötige die POSIX-Kompatibilität nicht. Einige Scripte würde ich auch nie in POSIX schreiben wollen.)
Ich nehme mal an, wenn es POSIX-kompatibel sein soll, muss die dmenu_cmd-Funktion ersetzt werden und die if-statements müssen überarbeitet werden, richtig?
Auf die schnelle habe ich nur 2 ifs gefunden, die bash-Syntax sind:

Code: Alles auswählen

	if [[ "$input" = "4"* ]]; then
	elif [[ "$input" = 'q' ]]; then
Da müsste man in POSIX ein "case" für nehmen, weil POSIX keinen Test mit Wildcards in if erlaubt (wohl aber in "case").

Ändere doch einfach das "#!/bin/bash" nach "#!/bin/sh", dann wirst du ja sehen, was nicht mehr funktioniert :mrgreen: Shellcheck sollte anschließend auch die Dinge anmeckern, die nicht POSIX sind.
Wenn es dagegen rein für bash sein soll, würde man vielleicht die vielen if durch case ersetzen?
Das kannst du so oder so machen, egal ob POSIX oder bash.
Oder ist das Blödsinn bei meinen recht kleinen Aufgaben, weil man den Zeitgewinn von 0,002s oder so eh nicht merkt?
Ich persönlich tendiere zu Blödsinn, versuche aber trotzdem build-ins zu verwenden statt externe Anwendungen, wo es problemlos möglich ist.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 30.12.2017 23:34:36

Hmja,

shellcheck hat wenig zu meckern, wenn ich es als POSIX-Script testen lasse und funktionieren tut außer echo -e (was auch shellcheck bemängelte) bei kurzem Test auch alles. Das -e bei echo ist bei POSIX offensichtlich auch gar nicht nötig, weil die newlines eh korrekt interpretiert werden.

Also POSIX wäre leicht zu erreichen.

Aber meinst Du, dass ich mein Script wesentlich effizienter machen könnte, wenn ich nur für bash schreiben würde?
Was könnte ich da grob gesagt modifizieren?
Das größte Problem, vor dem ich mich in diesem Fall sehe, ist folgendes:
Beispiel: Ich möchte den Output eines Kommandos verändern. Ich möchte z.B. vor "prefixstring" das "prefix" entfernen. Dann geht das zwar mit der Pattern Substitution "${parameter/pattern/string}" sehr schön, aber "parameter" muss immer eine Variable sein, sodass ich nie Pipes nutzen kann, sondern immer erstmal den Ouptut in eine Variable verpacken muss.
Geht das auch anders?

owl102

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von owl102 » 31.12.2017 00:47:27

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
30.12.2017 23:34:36
Das -e bei echo ist bei POSIX offensichtlich auch gar nicht nötig, weil die newlines eh korrekt interpretiert werden.
Nein. :wink: Bezüglich POSIX ist bei echo nur sehr wenig definiert, der Schalter "-e" ist AFAIK nicht Bestandteil von POSIX und die Interpretation von "\n" und ähnlichem auch nicht.

printf ist hingegen bei POSIX gut dokumentiert und daher würde ich bei sowas lieber printf statt echo einsetzen.

Das Problem bei POSIX ist, daß es keine Shell gibt, die nur POSIX kann. Unter Debian ist zum Beispiel die /bin/sh in Wirklichkeit die dash (Beweis: "ls -l /bin/sh"), die neben POSIX auch ein bischen mehr kann. Unter Fedora/RHEL ist übrigens /bin/sh die bash, die ja auch neben POSIX ein bischen mehr kann. Unter RHEL könnte man also "#!/bin/sh" in die erste Zeile schreiben und reichlich bash-Code produzieren, der auf RHEL problemlos funktioniert, aber unter Debian nicht.

Nur weil es funktioniert, heißt das folglich nicht, daß es POSIX ist, und schon gar nicht, daß es auch mit anderen POSIX-kompatiblen Shells funktioniert. Man muß wissen, was der POSIX-Standard hergibt (und was nicht).
Aber meinst Du, dass ich mein Script wesentlich effizienter machen könnte, wenn ich nur für bash schreiben würde?
Dieses Script? Nein, denke ich nicht. Bash kann zum Beispiel Arrays, POSIX nicht. Wenn man also eine Aufgabe hat, die sich mit Arrays elegant lösen läßt, kommt bei bash eine elegantere Lösung heraus als bei POSIX. Usw.
Dann geht das zwar mit der Pattern Substitution "${parameter/pattern/string}" sehr schön, aber "parameter" muss immer eine Variable sein, sodass ich nie Pipes nutzen kann, sondern immer erstmal den Ouptut in eine Variable verpacken muss.
Geht das auch anders?
Ja, zum Beispiel mit sed.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 31.12.2017 12:32:19

Hallo,

ich habe jetzt erstmal noch zwei wichtige Probleme behoben, die ich noch entdeckt habe:
1. Prüfen der Zugangsberechtigung bei Ordnern und Dateien vor dem Öffnen
2. Die Ermittlung des Ordners bei einem string wie "drwxr-xr-x 2 robert robert 4.0K Dec 31 12:17 .test test:drei/" (wie er im erweiterten Modus vorkommen könnte) hat mit meiner vorherigen Lösung nicht funktioniert, bei der alles bis zum letzten Leerzeichen gelöscht wurde. Nun lösche ich alles bis zum ersten Doppelpunkt plus drei zustätzliche Zeichen (nämlich die Minuten und das Leerzeichen).

@owl102: Also ich entscheide mich für dieses Script jetzt erstmal für rein bash. Hättest Du dann noch Verbesserungsvorschläge?

Aktuelles Script sieht so aus:

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

set -e

dmenu_cmd () {
	dmenu -i -l 25
}
apps_path="/home/robert/.local/share/applications/"

while true; do
if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
show=''
list="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)"
if [ -d "$list" ]; then
	if [ -x "$list" ]; then
		cd "$list"
	else
		echo "$list: Permission denied" | dmenu_cmd > /dev/null
	fi
elif [ -f "$list" ]; then
	if [ -r "$list" ]; then
		type=$(file -b --mime-type "$list")
		tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
		if [ ! -r "$tool" ]; then exit; fi
		if grep -q 'Terminal=true' "$tool"; then
			urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") $list"
		else
			exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list" &
			exit
		fi
	else
		echo "$list: Permission denied" | dmenu_cmd > /dev/null
	fi
else
	if [ "$list" = '+' ]; then
		while true; do
		if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
		show=''
		input="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -lAh --group-directories-first -F)" | dmenu_cmd)"
		if [[ "$input" = "4"* ]]; then
			COM="${input#4}"
			if eval "$COM"; then
				continue
			else
				show='An error occurred. Please press Enter.'
				continue
			fi
		elif [[ "$input" = 'q' ]]; then
			continue 2
		else
			list2="${input#*:???}"
			if [ -d "$list2" ]; then
				if [ -x "$list2" ]; then
					cd "$list2"
				else
					echo "$list2: Permission denied" | dmenu_cmd > /dev/null
				fi
			elif [ -f "$list2" ]; then
				if [ -r "$list2" ]; then
					type=$(file -b --mime-type "$list2")
					tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
					if [ ! -r "$tool" ]; then exit; fi
					if grep -q 'Terminal=true' "$tool"; then
						urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") $list2"
					else
						exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/Exec=//p' "$tool") "$list2" &
						exit
					fi
				else
					echo "$list2: Permission denied" | dmenu_cmd > /dev/null
				fi
			else
				show='An error occurred. Please press Enter.'
			fi
		fi
		done
	else
		if eval "$list"; then
			continue
		else
			show='An error occurred. Please press Enter.'
			continue
		fi
	fi
fi
done

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 03.01.2018 16:57:24

Hallo,

ich weiß nicht, ob's jemanden interessiert, aber irgendwie kann ich auch keine alte Version stehen lassen. Jedenfalls hier die aktuelle:

Leider kann ich die beiden blöden evals nicht in Funktionen umwandeln, weil dann bei Befehlen wie "mkdir "test test"" gar nix mehr klappt... Dafür funktionieren jetzt immerhin überhaupt Befehle, die Datei- oder Ordnernamen mit Leerzeichen enthalten. Habe jetzt auch endlich die Verwendung eines Befehls eindeutig gemacht: Wenn vor dem Output von dmenu ein "///" steht, ist es als Befehl zu behandeln, also mit eval auszuführen. Drei "/" hintereinander kann auf jeden Fall in keinem anderen Kontext vorkommen...
Habe auch noch geändert, dass der file manager immer im Hintergrund offen bleibt, wenn eine Anwendung im Terminal geöffnet wird und nach Schließen der letzteren wieder zur Verfügung steht. Nun ist das nur noch im erweiterten Modus der Fall, dafür aber auch bei GUI-Anwendungen.

Wenn jemand noch Verbesserungsvorschläge hat, immer her damit!

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

set -e

dmenu_cmd () {
	dmenu -i -l 25
}
apps_path="/home/robert/.local/share/applications/"

while true; do
if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
unset show
list="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)"
if [ -d "$list" ]; then
	if [ -x "$list" ]; then
		cd "$list"
	else
		echo "$list: Permission denied" | dmenu_cmd > /dev/null
	fi
elif [ -f "$list" ]; then
	if [ -r "$list" ]; then
		type=$(file -b --mime-type "$list")
		tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
		if [ ! -r "$tool" ]; then exit; fi
		if grep -q 'Terminal=true' "$tool"; then
			urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") $list" &
			exit
		else
			exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") "$list" &
			exit
		fi
	else
		echo "$list: Permission denied" | dmenu_cmd > /dev/null
	fi
else
	if [ "$list" = '///+' ]; then
		while true; do
		if [ -n "$show" ]; then echo "$show" | dmenu_cmd > /dev/null; fi
		unset show
		input="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -lAh --group-directories-first -F)" | dmenu_cmd)"
		case "$input" in
		"///q")
			continue 2
			;;
		"///"*)
			COM="${input#\/\/\/}"
			if eval "$COM"; then
				continue
			else
				show='An error occurred. Please press Enter.'
				continue
			fi
			;;
		*)
			list2="${input#*:???}"
			if [ -d "$list2" ]; then
				if [ -x "$list2" ]; then
					cd "$list2"
				else
					echo "$list2: Permission denied" | dmenu_cmd > /dev/null
				fi
			elif [ -f "$list2" ]; then
				if [ -r "$list2" ]; then
					type=$(file -b --mime-type "$list2")
					tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
					if [ ! -r "$tool" ]; then exit; fi
					if grep -q 'Terminal=true' "$tool"; then
						urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") $list2"
					else
						exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") "$list2"
					fi
				else
					echo "$list2: Permission denied" | dmenu_cmd > /dev/null
				fi
			else
				show='An error occurred. Please press Enter.'
			fi
			;;
		esac
		done
	else
		case "$list" in
		"///"*)
			COM2="${list#\/\/\/}"
			if eval "$COM2"; then
				continue
			else
				show='An error occurred. Please press Enter.'
				continue
			fi
			;;
		*)
			show='An error occurred. Please press Enter.'
			continue
			;;
		esac
	fi
fi
done

owl102

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von owl102 » 03.01.2018 19:10:00

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
03.01.2018 16:57:24
ich weiß nicht, ob's jemanden interessiert
Ich hoffe ich komme am Wochenende dazu. In der Woche ist's immer schlecht.

Vorab: Ich bin kein Fan von "set -e". Ich verwende es zwar auch, aber nur in hingerotzten Scripten, die ich hin-und-wieder manuell anstoße. Bei "richtigen" Scripten hingegen nicht, denn: "set -e" fängt nicht alle Probleme ab, dafür aber welche, die gar keine sind:

Code: Alles auswählen

#!/bin/bash
set -e
i=0
((i++))
echo "i=$i"
Außerdem würde ich "[..]" meiden und stattdessen ausschließlich "[[..]]" bzw. "((..))" verwenden. Überhaupt neige ich dazu, allem in http://mywiki.wooledge.org/BashGuide zuzustimmen.

Des weiteren verstehe ich die Notwendigkeit der "show"-Konstruktion nicht. Warum nicht einfach stattdessen eine Funktion "show" anbieten, die man aufruft?
Leider kann ich die beiden blöden evals nicht in Funktionen umwandeln
Wie hattest du das versucht?

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 04.01.2018 13:21:12

owl102 hat geschrieben: ↑ zum Beitrag ↑
03.01.2018 19:10:00
Ich hoffe ich komme am Wochenende dazu. In der Woche ist's immer schlecht.
Ich wollte Dir keinen Vorwurf machen. Ich habe kein Problem damit, wenn das Thema dmenu-Script hier im Forum gerade nicht so auf Interesse stößt, nur dann würde ich halt das Thema hier nicht unbedingt weiter führen. Und schon gar nicht habe ich ein Problem damit, wenn jemand gerade nicht die Zeit findet, hier etwas zu schreiben!
Aber wenn Du mir noch ein paar Tipps geben möchtest, freue ich mich selbstverständlich.

Deine Idee mit der Funktion anstelle der "show-Konstruktion" ist toll! Das habe ich gleich mal umgesetzt und die Gelegenheit genutzt, das auch noch für die Permission-denied Anzeige so zu machen und noch eine weitere Anzeige zu implementieren, wenn für den ermittelten file-type keine Anwendung in "mimeapps.list" gefunden wird.

Die evals habe ich versucht zu ersetzen durch folgende Konstruktion:
COM () {
"${input#\/\/\/}" bzw. ${input#\/\/\/} (also ohne Anführungsstriche)
}
und dann weiter mit "if COM; then" und so weiter.
Aber wenn ich das einzeln im Terminal teste:
$ TEST="$(echo '///mkdir "test test"' | dmenu)" #Nun öffnet sich dmenu, ich kann meinen Eintrag wählen, worauf ihn dmenu nach stdout schreibt, also in die Variable TEST.
$ TESTC () { "${TEST#\/\/\/}";
$ TESTC
bash: mkdir "test test": command not found
oder ohne Anführungszeichen:
$ TESTC () { ${TEST#\/\/\/}; }
$ TESTC
$ ls -la
drwxr-xr-x 2 robert robert 4096 Jan 4 13:04 "test
drwxr-xr-x 2 robert robert 4096 Jan 4 13:04 test"

Das ist nicht schön...

Auf Deinen Vorschlag hin habe ich auch die [] durch [[]] ersetzt. In deinen Link habe ich bezüglich der if-statements auch schon reingelesen. Das ist sehr interessant und ausfürhlich, vielen Dank! Bei tldp.org, wo ich bisher manchmal reingeschaut habe, hatte ich nicht so eine ausführliche Dokumentation gefunden.

Aktuelle Version:

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

set -e

dmenu_cmd () {
	dmenu -i -l 25
}
show_cmd () {
	echo 'An error occurred. Please press Enter.' | dmenu_cmd > /dev/null
}
perm_cmd () {
	echo "$list: Permission denied" | dmenu_cmd > /dev/null
}
noapp_cmd () {
	echo "There's no app specified for $type in mimeapps.list" | dmenu_cmd > /dev/null
}
apps_path="/home/robert/.local/share/applications/"

while true; do
input="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)"
case "$input" in
*"*")
	listtmp="${input#*:???}" && list="${listtmp%?}"
	;;
*)
	list="${input#*:???}"
	;;
esac
if [[ -d "$list" ]]; then
	if [ -x "$list" ]; then
		cd "$list"
	else
		perm_cmd
	fi
elif [[ -f "$list" ]]; then
	if [[ -r "$list" ]]; then
		type=$(file -b --mime-type "$list")
		tool=$(grep -m 1 "$type" "$HOME/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
		if [[ ! -r "$tool" ]]; then noapp_cmd && continue; fi
		if grep -q 'Terminal=true' "$tool"; then
			urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") $list" &
			exit
		else
			exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") "$list" &
			exit
		fi
	else
		perm_cmd
	fi
else
	case "$list" in
	'///+')
		while true; do
		input="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -lAh --group-directories-first -F)" | dmenu_cmd)"
		case "$input" in
		"///q")
			continue 2
			;;
		"///"*)
			COM="${input#\/\/\/}"
			if eval "$COM"; then
				continue
			else
				show_cmd
				continue
			fi
			;;
		*)
			case "$input" in
			*"*")
				listtmp="${input#*:???}" && list="${listtmp%?}"
				;;
			*)
				list="${input#*:???}"
				;;
			esac
			if [[ -d "$list" ]]; then
				if [[ -x "$list" ]]; then
					cd "$list"
				else
					perm_cmd
				fi
			elif [[ -f "$list" ]]; then
				if [[ -r "$list" ]]; then
					type=$(file -b --mime-type "$list")
					tool=$(grep -m 1 "$type" "/home/robert/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)
					if [[ ! -r "$tool" ]]; then noapp_cmd && continue; fi
					if grep -q 'Terminal=true' "$tool"; then
						urxvt -e bash -c "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") $list"
					else
						exec $(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool") "$list"
					fi
				else
					perm_cmd
				fi
			else
				show_cmd
			fi
			;;
		esac
		done
		;;
	*)
		case "$list" in
		"///"*)
			COM2="${list#\/\/\/}"
			if eval "$COM2"; then
				continue
			else
				show_cmd
			fi
			;;
		*)
			show_cmd
			;;
		esac
		;;
	esac
fi
done

owl102

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von owl102 » 11.01.2018 20:46:20

Neue Anmerkungen:

"/home/robert" würde ich durch "$HOME" ersetzen. (Kommt ein paar mal vor)

Code: Alles auswählen

input="$(echo -e "$(pwd)\n../\n$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)"
würde ich durch

Code: Alles auswählen

input="$(printf '%s\n../\n%s\n' "$(pwd)" "$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)"
ersetzen.

Hintergrund: "echo -e" ist nicht durch POSIX gedeckt, Da dies ein Bash-Script ist, ist das egal, denn bash kann "echo -e". Ich sehe aber bei mir die Gefahr, daß wenn ich mir sowas erst angewöhne, ich es auch in POSIX-Scripten einsetze. Außerdem kann es sein, daß in $pwd ein Backslash enthalten sein kann, der dann fälschlicherweise interpretiert werden würde. Dieses Problem vermeidet man, indem man $pwd nur als Argument von printf einsetzt und nicht direkt in den Format-String einsetzt. Dito bei $(ls). Insgesamt ist es immer eine gute Idee, bei Zeichenketten, wo man nicht sicher sein kann, was da drin steht, immer über "printf '...%s...' <zeichenkette>" zu gehen.

Code: Alles auswählen

cd "$list"
würde ich durch

Code: Alles auswählen

cd -- "$list"
ersetzen, denn man weiß nicht, ob der Verzeichnisname nicht mit "-" oder "--" anfängt, und das würde dann als Option interpretiert werden. Das "--" verhindert dies. Davon gibt es auch noch mehr Stellen in deinem Script, zum Beispiel

Code: Alles auswählen

type=$(file -b --mime-type -- "$list")

Code: Alles auswählen

if [[ ! -r "$tool" ]]; then noapp_cmd && continue; fi
Ist das wirklich so gemeint, daß wenn noapp_cmd einen Rückgabewert von ungleich 0 hat, das "continue" nicht ausgeführt werden soll? Wenn nicht, dann wäre richtiger:

Code: Alles auswählen

if [[ ! -r "$tool" ]]; then noapp_cmd; continue; fi
Über "set -e" hatte ich ja schon was geschrieben. Ich würde stattdessen lieber

Code: Alles auswählen

type=$(file -b --mime-type -- "$list")
durch

Code: Alles auswählen

if type=$(file -b --mime-type -- "$list"); then ...
usw. ersetzen.

Code: Alles auswählen

grep -q 'Terminal=true' "$tool"
Ich bin jetzt zu faul, die Syntax der Einträge nachzuschauen. Ich hätte eher sowas wie

Code: Alles auswählen

grep -q -i '^[[:blank:]]*terminal=true' -- "$tool"
erwartet, schließlich soll zum Beispiel "XTerminal=true" nicht fälschlicherweise gefunden werden. Außerdem würde ich da ein "--" einsetzen. Und "-i"!?

"type=...; tool=..." kommt mehrfach vor. Ebenso "$(sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' "$tool")". Da würde ich jeweils eine Funktion für nehmen. (Und bei dem sed wieder ein "--" vor "$tool" einbauen.)

Mir fehlen Kommentare, insbesondere mit beispielhaftem Input und Output. Ich müsste daher selber Debugzeilen a la "printf ..." einbauen, um das Script nachvollziehen zu können, da bin ich aber jetzt zu faul zu. :wink:

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 12.01.2018 14:07:11

Danke, das war sehr hilfreich!

Das mit dem "--" habe ich noch gar nicht gewusst, das ist wirklich nützlich.
Wo sinnvoll, habe ich wie von Dir vorgeschlagen, noch weitere Variablen durch Funktionen ersetzt, jetzt sieht das Script schon bedeutend übersichtlicher aus... Bei der Funktion "TOOL" habe ich noch die drei Schritte durch ein einziges sed-Kommando (+ builtin parameter substitution) ersetzt, was laut time zu einer Geschwindigkeitssteigerung von 0,006s auf 0,002s für das Kommando führt.
Funktionen, die den gleichen Namen wie eine Variable tragen, habe ich zum besseren Verständnis in Großbuchstaben geschrieben, auch wenn ich durch Tests erfahren habe, dass das nicht nötig wäre.
Kommentare zur besseren Nachvollziehbarkeit sind eine sehr gute Idee, auch für mich selber: Durch dieses Verfahren habe ich beim ersten "case $input" zwei völlig überflüssige Variablen gefunden und entfernt.

"set -e" habe ich gestrichen, aber statt Deinem Vorschlag:

Code: Alles auswählen

if type=$(file -b --mime-type -- "$list"); then ...
habe ich mir Folgendes überlegt, das mir eleganter und genauso passend erschien:

Code: Alles auswählen

type="$(TYPE)" || exit
Dieses "|| exit" habe ich einfach überall angehängt, wo es mir sinnvoll schien. Ist das gut so?
Die Konstruktion "||" habe ich auch zum Testen von "eval $COM" und "eval $COM2" anstatt des zuvor verwendeten if verwendet.

zu Deinem Vorschlag:

Code: Alles auswählen

grep -q -i '^[[:blank:]]*terminal=true'
Das "^" ist eine sehr gute Idee, das habe ich ergänzt. Aber wozu ist das [[:blank:]] dann noch gut? Das "-i" ist glaube ich nicht nötig, wenn man die Spezifikationen von freedesktop.org betrachtet: https://standards.freedesktop.org/deskt ... 01s06.html
Ist da eine abweichende Groß-Kleinschreibung erlaubt?

Aktuelle Version:

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

dmenu_cmd () { dmenu -i -l 25; }

TYPE () { file -b --mime-type -- "$list"; }

TOOL () { sed -n "/^${type//\//\\/}/ {s@^.*=@$apps_path@;s/\;.*//p;q}" -- "$HOME/.config/mimeapps.list"; }
# the parameter substitution replaces the type, for example "text/plain", with "text\/plain", i.e. the slash is escaped.
# Because the given parameter ("/" in this case) begins with a slash, all matches of the given pattern are replaced, i.e.
# "example/text/plain" is replaced with "example\/text\/plain". IMHO, there aren't any mimetypes with two slahes but we
# can't be careful enough, can we?
# old command: tool=$(grep -m 1 "$type" "$HOME/.config/mimeapps.list" | sed "s@^.*=@$apps_path@" | cut -d ';' -f1)

COMPPRE () { sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' -- "$tool"; }

TESTTERM () { grep -q '^Terminal=true' -- "$tool"; }

show_cmd () { echo 'An error occurred. Please press Enter.' | dmenu_cmd > /dev/null; }

perm_cmd () { echo "$list: Permission denied" | dmenu_cmd > /dev/null; }

noapp_cmd () { echo "There's no app specified for $type in mimeapps.list" | dmenu_cmd > /dev/null; }

apps_path="$HOME/.local/share/applications/"

while true; do
input="$(printf '%s\n../\n%s\n' "$(pwd)" "$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)" || exit
# example: input="Dokumente/" or input="testscript.sh*" or input="///touch "test file"" or input="///rm testscript.sh*"
# or input="///+" to enter the extended view
case "$input" in
*"*")
	list="${listtmp%?}"
# example: list="testscript.sh" or list="///rm testscript.sh" - the asterisk has to be removed
	;;
*)
	list="$input"
# no modifications needed - slash after directory name is allowed
	;;
esac
if [[ -d "$list" ]]; then
	if [ -x "$list" ]; then
		cd -- "$list" || exit
	else
		perm_cmd
	fi
elif [[ -f "$list" ]]; then
	if [[ -r "$list" ]]; then
		type="$(TYPE)" || exit
# example: type="text/plain"
		tool="$(TOOL)" || exit
# example: tool="/home/robert/.local/share/applications/nano.desktop"
		if [[ ! -r "$tool" ]]; then noapp_cmd; continue || exit; fi
		if TESTTERM; then
			urxvt -e bash -c -- "$(COMPPRE) -- $list" &
# COMPPRE searches the command to execute in the given .desktop file
			exit
		else
			exec $(COMPPRE) -- "$list" &
			exit
		fi
	else
		perm_cmd
	fi
else
	case "$list" in
	'///+')
		while true; do
		input="$(printf '%s\n../\n%s\n' "$(pwd)" "$(LC_ALL="C" ls -lAh --group-directories-first -F)" | dmenu_cmd)" || exit
# example: input="drwx------  2 robert robert 4.0K Aug 13 21:19 .aptitude/" or input="-rwxr-xr-x  1 robert robert  204 Jan 10 14:03 testscript.sh*"
# or input="///touch "test file"" or input="///q" to quit the extended view and return to simple view
		case "$input" in
		"///q")
			continue 2 || exit
			;;
		"///"*)
			COM="${input#\/\/\/}"
# if input is a command, we need to remove the three slashes and then execute the command using eval
# example: COM="touch "test file""
			eval "$COM" || show_cmd
			continue || exit
			;;
		*)
			case "$input" in
			*"*")
				listtmp="${input#*:???}" && list="${listtmp%?}"
# we have to remove all information from ls but the file or directory name - again, an asterisk has to be removed as we did above
# example: list=".aptitude/" or list="testscript.sh"
				;;
			*)
				list="${input#*:???}"
# we have to remove all information from ls but the file or directory name
				;;
			esac
			if [[ -d "$list" ]]; then
				if [[ -x "$list" ]]; then
					cd -- "$list" || exit
				else
					perm_cmd
				fi
			elif [[ -f "$list" ]]; then
				if [[ -r "$list" ]]; then
# the same procedure as above:
					type="$(TYPE)" || exit
					tool="$(TOOL)" || exit
					if [[ ! -r "$tool" ]]; then noapp_cmd; continue || exit; fi
					if TESTTERM; then
						urxvt -e bash -c -- "$(COMPPRE) -- $list"
					else
						exec $(COMPPRE) -- "$list"
					fi
				else
					perm_cmd
				fi
			else
				show_cmd
			fi
			;;
		esac
		done
		;;
	*)
		case "$list" in
		"///"*)
			COM2="${list#\/\/\/}"
# if input is a command, we need to remove the three slashes and then execute the command using eval
			eval "$COM2" || show_cmd
			continue || exit
			;;
		*)
			show_cmd
			;;
		esac
		;;
	esac
fi
done

owl102

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von owl102 » 12.01.2018 16:45:34

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
12.01.2018 14:07:11
Dieses "|| exit" habe ich einfach überall angehängt, wo es mir sinnvoll schien. Ist das gut so?
Die Frage ist, was du möchtest. Wenn du möchtest, daß sich das Programm bei Problemen einfach beendet, ist das ok so.
Aber wozu ist das [[:blank:]] dann noch gut?
Mit "[[:blank:]]*" würde man auch optional Leerzeichen/Tabs vor dem "Terminal=true" erlauben. Ob das die Syntax der Datei hergibt, weiß ich nicht.
Das "-i" ist glaube ich nicht nötig, wenn man die Spezifikationen von freedesktop.org betrachtet
Habe auch gerade mal nachgeschaut, du hast Recht, sowohl bei "Terminal" als auch bei "true" ist keine abweichende Groß-/Kleinschreibung erlaubt.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 29.01.2018 14:40:33

Hallo,

ich habe es jetzt nochmal überarbeitet und das Anlegen eines error-logs eingebaut, also zwischen jedem "||" und "exit" steht nun noch " >> "$logfile" 2>&1". Für die logs habe ich einfach in $HOME einen Ordner .var angelegt...
Außerdem habe ich doch noch die Funktionen von der Schreibweise her vereinheitlicht. Da ich mittlerweile vim nutze (habe mich endlich dafür bereit gefühlt) ist das jetzt ganz locker mit ":%s/string/newstring/gc" sogar mit Nachfrage zu erledigen...

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

dmenu_cmd () { dmenu -i -l 25; }

type_cmd () { file -b --mime-type -- "$list"; }

tool_cmd () { sed -n "/^${type//\//\\/}/ {s@^.*=@$apps_path@;s/\;.*//p;q}" -- "$HOME/.config/mimeapps.list"; }

comppre_cmd () { sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' -- "$tool"; }

testterm_cmd () { grep -q '^Terminal=true' -- "$tool"; }

show_cmd () { echo 'An error occurred. Please press Enter.' | dmenu_cmd > /dev/null; }

perm_cmd () { echo "$list: Permission denied" | dmenu_cmd > /dev/null; }

noapp_cmd () { echo "There's no app specified for $type in mimeapps.list" | dmenu_cmd > /dev/null; }

apps_path="$HOME/.local/share/applications/"
logfile="$HOME/.var/fm.log"

while true; do
input="$(printf '%s\n../\n%s\n' "$(pwd)" "$(LC_ALL="C" ls -1 --group-directories-first -F)" | dmenu_cmd)" >> "$logfile" 2>&1 || exit
case "$input" in
*"*")
	list="${listtmp%?}"
	;;
*)
	list="$input"
	;;
esac
if [[ -d "$list" ]]; then
	if [ -x "$list" ]; then
		cd -- "$list" >> "$logfile" 2>&1 || exit
	else
		perm_cmd
	fi
elif [[ -f "$list" ]]; then
	if [[ -r "$list" ]]; then
		type="$(type_cmd)" >> "$logfile" 2>&1 || exit
		tool="$(tool_cmd)" >> "$logfile" 2>&1 || exit
		if [[ ! -r "$tool" ]]; then noapp_cmd; continue >> "$logfile" 2>&1 || exit; fi
		if testterm_cmd; then
			urxvt -e bash -c -- "$(comppre_cmd) -- $list" &
		else
  			exec $(comppre_cmd) "$list" &
		fi
		exit
	else
		perm_cmd
	fi
else
	case "$list" in
	'///+')
		while true; do
		input="$(printf '%s\n../\n%s\n' "$(pwd)" "$(LC_ALL="C" ls -lAh --group-directories-first -F)" | dmenu_cmd)" >> "$logfile" 2>&1 || exit
		case "$input" in
		"///q")
			continue 2 >> "$logfile" 2>&1 || exit
			;;
		"///"*)
			COM="${input#\/\/\/}"
			eval "$COM" >> "$logfile" 2>&1 || show_cmd
			continue >> "$logfile" 2>&1 || exit
			;;
		*)
			case "$input" in
			*"*")
				listtmp="${input#*:???}" && list="${listtmp%?}"
				;;
			*)
				list="${input#*:???}"
				;;
			esac
			if [[ -d "$list" ]]; then
				if [[ -x "$list" ]]; then
					cd -- "$list" >> "$logfile" 2>&1 || exit
				else
					perm_cmd
				fi
			elif [[ -f "$list" ]]; then
				if [[ -r "$list" ]]; then
					type="$(type_cmd)" >> "$logfile" 2>&1 || exit
					tool="$(tool_cmd)" >> "$logfile" 2>&1 || exit
					if [[ ! -r "$tool" ]]; then noapp_cmd; continue >> "$logfile" 2>&1 || exit; fi
					if testterm_cmd; then
						urxvt -e bash -c -- "$(comppre_cmd) -- $list"
					else
						exec $(comppre_cmd) "$list"
					fi
				else
					perm_cmd
				fi
			else
				show_cmd
			fi
			;;
		esac
		done
		;;
	*)
		case "$list" in
		"///"*)
			COM2="${list#\/\/\/}"
			eval "$COM2" >> "$logfile" 2>&1 || show_cmd
			continue >> "$logfile" 2>&1 || exit
			;;
		*)
			show_cmd
			;;
		esac
		;;
	esac
fi
done

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 29.03.2018 21:37:39

Ich habe mein Script nochmal gründlich überarbeitet. Quasi Version 2.0 :)
Einen großen Schwerpunkt habe ich dabei auf Funktionen gesetzt. Ich habe das immer häufiger gesehen und mir gefällt die Idee irgendwie gut.
Außerdem wollte ich auch den logischen Aufbau noch etwas verbessern.
Ist noch nicht ganz fertig (Comments fehlen auch noch), aber ich wollte mal fragen, was ihr von der Idee haltet, Variablen so häufig auf Inhalte zu testen ([[ -z $variable]]).
Wie macht man das so üblicherweise?

Anmerkung: Lasst euch nicht verwirren, ich nutze mittlerweile vim und habe das Script so formatiert, das überall nur 2 Leerzeichen Einschub sind. Das hat den Nebeneffekt, dass ich eine maximale Zeilenlänge von 80 Zeichen einhalten kann. (Gehört das nicht auch bei Scripten zum guten Ton?) Geht immerhin mit vim ganz praktisch: Alles auswählen und "=" drücken.

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

apps_path="$HOME/.local/share/applications/"

dmenu_cmd () {
  dmenu -i -l 25
}

input_short () {
  short_ls () { LC_ALL="C" ls -1 --group-directories-first -F; }
  printf '%s\n../\n%s\n' "$(pwd)" "$(short_ls)" | dmenu_cmd
}

input_verbose () {
  long_ls () { LC_ALL="C" ls -lAh --group-directories-first -F; }
  printf '%s\n../\n%s\n' "$(pwd)" "$(long_ls)" | dmenu_cmd
}

type_cmd () {
  file -b --mime-type -- "$list"
}

tool_cmd () {
  sed -n "/^${type//\//\\/}/ {s@^.*=@$apps_path@;s/\;.*//p;q}" -- \
    "$HOME/.config/mimeapps.list"
}

comppre_cmd () {
  sed -n '/\[Desktop Entry\]/,/^Exec=/!d;s/^Exec=//p' -- "$tool"
}

testterm_cmd () {
  grep -q '^Terminal=true' -- "$tool"
}

error_cmd () {
  printf '%s' 'An error occurred. Please press Enter.' | 
  dmenu_cmd > /dev/null
}

perm_cmd () {
  printf '%s' "$list: Permission denied" | dmenu_cmd > /dev/null
}

noapp_cmd () {
  printf '%s' "There's no app specified for $type in mimeapps.list" | 
  dmenu_cmd > /dev/null
}

exec_terminal () {
  urxvt -e bash -c -- "$(comppre_cmd) -- $list"
}

exec_gui () {
  exec $(comppre_cmd) "$list"
}

sed_cmd () {
  sed -E 's/([,.:a-zA-Z0-9-]*[[:space:]]*){8}//'
}

while true; do
  list="$(input_short)"; [[ -z $list ]] && exit
  case "$list" in *"*") list="${list%?}";; esac; [[ -z $list ]] && exit
  if [[ -d "$list" ]]; then
    if [[ -x "$list" ]]; then
      cd -- "$list" || { error_cmd && continue; }
    else
      perm_cmd && continue
    fi
  elif [[ -f "$list" ]]; then
    [[ ! -r "$list" ]] && { perm_cmd && continue; }
    type="$(type_cmd)"; [[ -z $type ]] && { error_cmd && continue; }
    tool="$(tool_cmd)"; [[ ! -r $tool ]] && { noapp_cmd; continue; }
    if testterm_cmd; then
      exec_terminal &
    else
      exec_gui &
    fi
    exit
  elif [[ $list == '//+' ]]; then
    while true; do
      input="$(input_verbose)"; [[ -z $input ]] && exit
      case "$input" in
	"//q")
	  continue 2
	  ;;
	"//"*)
	  eval "${input#\/\/}" || { error_cmd && continue; }
	  continue
	  ;;
	*)
	  list="$(sed_cmd <<< "$input")"; [[ -z $list ]] && { error_cmd && continue; }
	  case "$input" in
	    *"*")
	      list="${list%?}"; [[ -z $list ]] && { error_cmd && continue; }
	      ;;
	    *)
	      case "$input" in
		"../")
		  list="$input"; [[ -z $list ]] && { error_cmd && continue; }
		  ;;
	      esac
	      ;;
	  esac
	  if [[ -d "$list" ]]; then
	    if [[ -x "$list" ]]; then
	      cd -- "$list" || { error_cmd && continue; }
	    else
	      perm_cmd && continue
	    fi
	  elif [[ -f "$list" ]]; then
	    if [[ -r "$list" ]]; then
	      type="$(type_cmd)"; [[ -z $type ]] && { error_cmd && continue; }
	      tool="$(tool_cmd)"; [[ ! -r $tool ]] && { noapp_cmd; continue; }
	      if testterm_cmd; then
		exec_terminal
	      else
		exec_gui
	      fi
	    else
	      perm_cmd && continue
	    fi
	  else
	    error_cmd && continue
	  fi
	  ;;
      esac
    done
  else
    case "$list" in
      "//"*)
	eval "${list#\/\/}" || { error_cmd && continue; }
	continue
	;;
      *)
	error_cmd && continue
	;;
    esac
  fi
done

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 02.04.2018 19:24:23

Ich arbeite gerade an einer Möglichkeit, meinen file manager mit Plugins zu erweitern. (Um zum Beispiel .txt und .tex Dateien in $HOME/Dokumente immer mit einem bestimmten Kommando zu öffnen.)
Wusstet ihr, dass man Funktionen auch exportieren kann?

Code: Alles auswählen

export -f function
Danke bash! :THX: :THX:

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 04.04.2018 00:15:16

(Aktueller file-manager: so wie vorher, aber am Anfang noch

Code: Alles auswählen

[[ -z "$workdir" ]] && workdir="$HOME"
cd "$workdir" || exit
eingefügt.)

So, mein file-manager-init-Script, welches Plugins einbindet ist, funktioniert schon. Die Plugins können der Kommandozeile als Optionen mitgegeben werden, und zwar in der Form "-[Pfad zum Plugin]". Das Plugin muss eine ausführbare Datei sein und genau eine Zahl enthalten, die die Zeilenzahl angibt, in die das Plugin in meinem file-manager-script eingefügt wird.
Pfade sind bei mir immer /opt/scripts/irgendwas, also z.B. /opt/scripts/fm.sh für meinen file-manager und /opt/scripts/fm_init.sh für mein init-script.

Ablauf ist folgender:
init-Script looped durch die Argumente (welche auch einen Pfad ohne vorangestelltes "-" enthalten können, der dann als aktueller Arbeits-Ordner verwertet wird) und bereitet sie für die weitere Verwertung vor.
Anschließend muss sed den Pfad zum Plugin in die richtige Zeile meines file-manager-scripts packen.
Wenn mehrere Plugins eingebunden werden sollen, muss dabei natürlich berücksichtigt werden, dass die Zeilenzahl sich ändert. Wenn ich also z.B. zwei Plugins einfügen will, eines in Zeile 40 und eines in Zeile 80, muss ich das in Zeile 80 zuerst einfügen. Sonst muss mein Script so viel rechnen. Das hatte ich auch zuerst, aber es ist doch viel praktischer, die Plugins rückwärts einzufügen. :D Wenn ich eine Zeile in Zeile 80 einfüge, bleibt Zeile 40 immer noch Zeile 40.

Aber irgendwie kommt mir mein Sortiervorgang leicht kompliziert vor. Das liegt wohl daran, dass ich unbedingt will, dass die Zahl nicht an einer bestimmten Stelle im Dateinamen (! Name !, der Pfad wird vor dem Extrahieren der Zahlen abgeschnippelt) stehen muss und dass der Dateiname beliebig sein kann.
Vielleicht fällt jemandem von euch eine elegantere Lösung ein?
Das Problem ist ja eigentlich einfach: Sortiere alle Plugin-Pfade nach der im Dateinamen enthaltenen Zahl.
Herausgekommen ist dabei folgendes - siehe die ganzen tmpvars... :( (Text geht nach Script noch weiter.)

Code: Alles auswählen

#!/bin/bash

args=($@)
argsl=$(printf '%s\n' "${args[@]}")

while read line; do
  case "$line" in
    -*)
      plugin_path="${line#-}"
      if [[ -f "$plugin_path" ]] && [[ -x "$plugin_path" ]]; then
	plugins_tmp+=("$plugin_path")
	plugin_name="${plugin_path##*/}"
	linenumber+=(${plugin_name//[^0-9]/})
      else
	exit 1
      fi
      ;;
    *)
      if [[ -d "$line" ]] && [[ -x "$line" ]]; then
	workdir="$line"
      else
	exit 1
      fi
      ;;
  esac
done <<< "$argsl"

lnumberlist=$(printf '%s\n' "${linenumber[@]}")
linesort=$(sort -gr <<< "$lnumberlist")

while read line; do
  tmpvar=$(grep -nx $line <<< "$lnumberlist")
  tmpvar2="${tmpvar%:*}"
  tmpvar3=$(grep -nx $line <<< "$linesort")
  tmpvar4="${tmpvar3%:*}"
  plugins[$tmpvar4-1]="${plugins_tmp[$tmpvar2-1]}"
done <<< "$linesort"

if [[ -n "${plugins[@]}" ]]; then
  fm_file=$(cat /opt/scripts/fm.sh)
  for x in "${!plugins[@]}"; do
    "${plugins[x]}"
    fm_file="$(sed -e "${linenumber[x]} i \ \ \ \ \"${plugins[x]}\"\ \"\$list\"\ \&\&\ exit" <<< "$fm_file")"
  done
  eval "$fm_file"
else
  /opt/scripts/fm.sh
fi
Wie ihr seht, verändere ich nicht wirklich mein file-manager-script, sondern lese selbiges als Variable ein und verändere dann die Variable, die ich mit eval ausführe. Ist das eigentlich gleichwertig, ein Script als Datei auszuführen oder es als Variable mit eval auszuführen?

Ein Plugin sieht dann aus wie z.B. /opt/scripts/fm_plugin80.sh (ignoriert die gvim-Zeilen, die stellen nur mein ausgearbeitetes Text-Bearbeitungs-Design her... :mrgreen: ):

Code: Alles auswählen

#!/bin/bash

case "$(pwd)" in
  "$HOME/Dokumente"*)
    case "$1" in
      *.txt)
	gvim -c"e $1" -c"vnew | e $(pwd)/$1.meta | vertical-resize 39 | setlocal textwidth=35" -c'exe 2 "wincmd w"'
	exit 0
	;;
      *.tex)
	gvim -c"e $1" -c"vnew | vertical-resize 39 | setlocal textwidth=35" -c'exe 2 "wincmd w"'
	exit 0
	;;
      *)
	exit 1
    esac
    ;;
  *)
    exit 1
    ;;
esac
Wenn ich mich nun in $HOME/Dokumente befinde oder einem Unterordner davon, wird eine .txt- oder .tex-Datei mit dem jeweiligen gvim-Kommando geöffnet.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 04.04.2018 13:08:47

Sieg!! Dank eines Beitrags von @heisenberg hier im Forum brauche ich eval glaube ich gar nicht.
Muss schnell mal testen und ggbf. umschreiben.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 04.04.2018 15:36:51

wegen der Verwertung der Zeilennummern ist mir auch schon was anderes eingefallen...

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

[erledigt] Frage bzgl. Bash-Script (auf dmenu basierender file manager)

Beitrag von RobertDebiannutzer » 05.04.2018 18:32:59

So, jetzt läuft alles.
Mir ist aufgefallen, dass es sowieso schlauer ist, irgendwo die einzufügende Zeile vollständig zu hinterlegen, statt nur den Pfad des jeweiligen Plugins in ein festgelegtes sed-Kommando einzufügen.
Ich mache jetzt einfach in die dritte Zeile eines jeden Plugin-Scripts einen Kommentar mit dem dazugehörigen sed-Kommando. Und was wäre naheliegender, als da dann gleich auch die gewünschte Zeilenzahl am Anfang zu intergrieren, statt sie in den Dateinamen zu schreiben?
So sieht dann ein Beispiel-Plugin aus:

Code: Alles auswählen

#!/bin/bash

# 117i\ \ \ \ /opt/scripts/fm_documents.sh "$list" && exit

case "$(pwd)" in
  "$HOME/Dokumente"*)
    case "$1" in
      *.txt)
	gvim -c"e $1" -c"vnew | e $(pwd)/$1.meta | vertical-resize 39 | setlocal textwidth=35" -c'exe 2 "wincmd w"'
	exit 0
	;;
      *.tex)
	gvim -c"e $1" -c"vnew | vertical-resize 39 | setlocal textwidth=35" -c'exe 2 "wincmd w"'
	exit 0
	;;
      *)
	exit 1
    esac
    ;;
  *)
    exit 1
    ;;
esac
Das init-Script habe ich nun auch einfach in das bestehende file-manager-script integriert.
Man beachte die Konstruktion mit "bash <(echo "$fm_file")"
"man bash" sagt dazu:

Code: Alles auswählen

   Process Substitution
       Process substitution allows a process's input or output to be referred to using a filename.  It takes the form of <(list) or >(list).  The process list is run asynchronously, and its input or  output
       appears  as  a  filename.  This filename is passed as an argument to the current command as the result of the expansion.  If the >(list) form is used, writing to the file will provide input for list.
       If the <(list) form is used, the file passed as an argument should be read to obtain the output of list.  Process substitution is supported on systems that support named pipes (FIFOs) or the  /dev/fd
       method of naming open files.

       When available, process substitution is performed simultaneously with parameter and variable expansion, command substitution, and arithmetic expansion.
Ich verstehe das so, dass meine Variable nun wie ein normales Script gelesen wird.
Das sollte wohl eleganter sein als eval, denke ich...

Der Abschnitt meines file-manager-scripts, welcher die Initiation vornimmt (Rest kennt ihr ja):

Code: Alles auswählen

#!/bin/bash

# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software; you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

args=($@)

if [[ -n "${args[@]}" ]]; then
  argsl=$(printf '%s\n' "${args[@]}")
  while read line; do
    case "$line" in
      -*)
	plugin_path="${line#-}"
	if [[ -f "$plugin_path" ]] && [[ -x "$plugin_path" ]]; then
	  insert_tmp+=("$(sed -n "3 {s/# //p}" "$plugin_path")")
	else
	  exit 1
	fi
	;;
      *)
	if [[ -d "$line" ]] && [[ -x "$line" ]]; then
	  workdir="$line"
	  export workdir
	else
	  workdir="$HOME"
	fi
	;;
    esac
  done <<< "$argsl"
fi

insert_cmd=("$(printf '%s\n' "${insert_tmp[@]}" | sort -rg -ti -k1)")

if [[ -n "${insert_cmd[@]}" ]]; then
  fm_file=$(cat /opt/scripts/fm.sh)
  for x in "${!insert_cmd[@]}"; do
    fm_file="$(sed -e "${insert_cmd[x]}" <<< "$fm_file")"
  done
  bash <(echo "$fm_file") &
  exit 0
fi

apps_path="$HOME/.local/share/applications/"

[[ -z "$workdir" ]] && workdir="$HOME"
cd "$workdir" || exit
Sortiert werden die Plugins nach wie vor rückwärts nach der Nummer der Zeile, in die sie eingefügt werden sollen.

Ich denke, dann setze ich den Thread hier mal auf gelöst. Auch wenn ich vielleicht noch weitere Feinarbeit mache (z.B. Plugin-Scripte nur einmalig laden, ist vielleicht noch schneller), ist es mit dem Thread wohl mal gut...

Antworten