[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

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