Bash – Sebastians Blog https://sgaul.de Neues aus den Softwareminen Sat, 09 Feb 2019 21:22:22 +0000 de-DE hourly 1 https://wordpress.org/?v=6.1.1 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg Bash – Sebastians Blog https://sgaul.de 32 32 SSH-Passwort als Argument https://sgaul.de/2015/09/20/ssh-passwort-als-argument/ https://sgaul.de/2015/09/20/ssh-passwort-als-argument/#comments Sun, 20 Sep 2015 14:23:16 +0000 https://sgaul.de/?p=2795 Aus Sicherheitssicht ein Graus, in einigen Situationen ein einfacher Workaround: sshpass erlaubt es, den sonst interaktiven Passwortprompt beim Anmelden mittels SSH zu überspringen:

sshpass -p 'raspberry' ssh pi@mein-pi

Auf meinem Ubuntu war es nicht dabei, befindet sich aber in den Paketquellen:

sudo apt-get install sshpass
]]>
https://sgaul.de/2015/09/20/ssh-passwort-als-argument/feed/ 1
Spring ohne Bin-Präfix nutzen https://sgaul.de/2014/11/18/spring-ohne-bin-praefix-nutzen/ Tue, 18 Nov 2014 15:05:00 +0000 https://sgaul.de/?p=2707 Spring ohne Bin-Präfix nutzen weiterlesen]]> Spring lässt sich sehr einfach installieren: Gem eintragen und die Binstubs erzeugen:

bundle exec spring binstub --all

Um nun rake routes oder rails generate statt bin/rake routes oder bin/rails generate nutzen zu können, einfach die folgenden Funktionen in beispielsweise die bash_aliases eintragen:

rake() { if [ -f bin/rake ]; then bin/rake "$@"; else bundle exec rake "$@"; fi }
rails() { if [ -f bin/rails ]; then bin/rails "$@"; else bundle exec rails "$@"; fi }
rspec() { if [ -f bin/rspec ]; then bin/rspec "$@"; else bundle exec rspec "$@"; fi }

Mein Dankeschön für den Ansatz geht an Arne.

Um die ursprünglichen Varianten ohne Spring zu nutzen kann command verwendet werden:

command rake routes
]]>
Youtube für die Kommandozeile https://sgaul.de/2014/03/24/youtube-fuer-die-kommandozeile/ Mon, 24 Mar 2014 12:14:50 +0000 https://sgaul.de/?p=2571 MPS-Youtube ist ein Python-Programm für die Kommandozeile, mit dem man komfortabel Musik und Videos von Youtube abspielen und in lokalen Playlists verwalten kann. Zudem lassen sich Videos oder ihr Audioanteil auf die Festplatte herunterladen.

Installation

Die Installation läuft über PIP, eine Art Paketmanager für Pythonmodule ähnlich Rubygems.

sudo apt-get install python-pip

MPS-Youtube setzt von Haus aus auf Mplayer. Das lässt sich ändern, ich bin jedoch den Weg des geringsten Widerstands gegangen:

sudo apt-get install mplayer

Nun kann das eigentliche Programm mittels PIP installiert werden. Es scheint sich hierbei um eine globale Installation zu handeln, Sudo-Rechte waren bei mir erforderlich:

sudo pip install mps-youtube

Nun kann man die vim-ähnliche Oberfläche mittels mpsyt starten.
mpsyt

Anwendung

Audio und Video abspielen

Wie Vim wird das Tool über eine unten angelegte Kommandozeile gesteuert. Ein Slash löst eine Suche nach einem Video, ein Doppelslash die Suche nach einer Youtube-Playlist aus:

/Suchbegriff nach Einzelvideo
//Suchbegriff nach Playlist

Man erhält eine nummerierte Liste mit Ergebnissen. Videos lassen sich durch Eingabe der Nummer abspielen, Playlists in gleicher Weise öffnen. Zudem lassen sich mehrere Videos nacheinander abspielen, etwa 1-10 oder auch 1,5,10.

Fehlende Videoausgabe

Bei mir ist das Programm nach der Installation als Musikwerkzeug ausgelegt, von Videos wird lediglich die Tonspur ausgegeben. Dies kann man temporär umgehen, indem man den abzuspielenden Videonummern die Optionen -f für Fullscreen bzw. -w für den Fenstermodus übergibt. Will man diese Einstellungen als Standard setzen, kann man dies über einen der entsprechenden Set-Befehle machen:

set show_video 1
set fullscreen 1

Playlists

Statt nur der Nummer kann man bspw. mittels add 1-10 oder add 1-10 [Playlistname] die zehn ersten Suchergebnisse in eine lokale Playlist legen. Diese kann man mit vp („view playlist“) öffnen und Inhalte wie Suchergebnisse abspielen. Mit ls lassen sich Playlists auflisten, mit play [Playlistname oder -ID] lassen sich ganze Playlists abspielen.

Insbesondere an dieser Stelle gibt es recht viele, aber auch intuitive Möglichkeiten mit benannten und temporären Listen zu hantieren. Die Github-Seite des Projekts gibt hier recht gute Einblicke. Dem Programm selbst kann man eine kleine Befehlsübersicht mittels help playlists entlocken.

Jukebox und Raspberry Pi

Das Programm hat mich recht positiv überrascht. Es ist klein, schnell und somit vor allem im Vergleich mit der doch immer aufwändiger werdenden Youtube-Seite eine echte Alternative. Mir kommen vor allem der Einsatz als Jukebox und Youtube-Client für den Raspberry Pi in den Sinn. Wer etwas in dieser Richtung sucht, sollte mal eine Blick darauf werfen.

Gotbletu hat das Programm in einem informativen Video zusammengefasst (englisch).

]]>
Fuzzy-Search-Filter für die Bash https://sgaul.de/2013/07/07/fuzzy-search-filter-fur-die-bash/ Sun, 07 Jul 2013 13:24:33 +0000 https://sgaul.de/?p=2265 Fuzzy-Search-Filter für die Bash weiterlesen]]> Ich hatte kürzlich von meiner nicht hunderprozentig zufriedenstellenden Suche nach einer Fuzzy-Search für die Bash geschrieben. Das Problem ist nicht das Herausfiltern an sich, sondern die Gewichtung der Ergebnisse. Als Maß für die Güte eignet sich die Levenshtein-Distanz. Diese zählt die Notwendigen Ersetzungen, Einfügungen und Löschungen, die nötig sind, um String a in String b zu überführen. Je geringer diese sogenannte Distanz, desto ähnlicher sind sich die Strings und desto passender sind sie als Suchergebnis (zumindest in der Theorie).

Meine Suche nach einer einfachen Implementierung, die ich von der Bash aufrufen kann, ist leider erfolglos geblieben. Interessanterweise ist die Implementierung der Levenshtein-Distanz für eine Fuzzy-Search sehr einfach, so dass es sich auch in sperrigem Bash gut umsetzen lässt.

Nur Einfügungen = einfache Levenshtein-Distanz

Die Vereinfachung basiert auf der Tatsache, dass bei meiner Fuzzy-Search nur eingefügt, nie aber ein Zeichen gelöscht oder verändert wird. Suchen wir unscharf nach abc, so ergeben sich beide Strings als Treffer:

cat $file_to_be_filtered
xxxaxxxbxxxcxxx
yyyaybcyyy

Im nächsten Schritt schneiden wir alles weg, was nicht benötigt wird, um den String matchen zu lassen (hier muss non-greedy gesucht werden, um den kleinst-möglichen Treffer zu finden):

... | grep -oP .*?a.*?b.*?c.*?
axxxbxxxc
aybc

Nun zählen wir die verbliebene Zeichenlänge und ziehen die Länge des Fuzzy-Suchstrings (|abc| = 3) ab und ermitteln somit die Levenshtein-Distanz.

axxxbxxxc # 9 - 3 = 6
aybc      # 4 - 3 = 1

Die Länge des Suchstrings ist konstant und hat auf die Ordnung keinen Einfluss. Sie kann in der Implementierung weggelassen werden.

... | awk '{ print length }' | sort | head -1

Implementierung in Bash

Hier mal eine einfache Umsetzung, die eine Datei unscharf durchsucht und die Ergebnisse nach aufsteigender Levenshtein-Distanz ausgibt. Der Algorithmus arbeitet mit einer temporären Datei, da sich diese in meinen Augen sehr viel einfacher bearbeiten lassen als Arrays oder ähnliches. Da die Datei nur virtuell im Arbeitsspeicher angelegt wird, sollte dies aber kein Problem darstellen.

#!/bin/bash

file_to_be_filtered="$1"
filter_string="$2"
# use in-memory directory /dev/shm/
result_file=/dev/shm/.fuzzyfilterresults

if [ ! -f $file_to_be_filtered ]; then
	echo "fuzzy_filter: '$file_to_be_filtered': No such file"
	exit 1
fi

# empty result file
> $result_file
# replace abc with .*?a.*?b.*?c.*?
# ? triggers non-greedy search in Perl
fuzzy_filter_string=$(echo "$filter_string" | sed 's/./&.*?/g')

while read line; do
	# get matches only (grep -o) with Perl syntax (-P)
	# replace them by their string length (awk)
	# return the smallest number (sort | head -1)
	match_length=$(echo "$line" | grep -oP "$fuzzy_filter_string" | awk '{ print length }' | sort | head -1)
	# check if variable is a number and not 0
	if [ "$match_length" -eq "$match_length" ] 2>/dev/null && [ "$match_length" -gt "0" ]; then
		# write number, space and original content into result file
		line="$match_length $line"
		echo $line >> $result_file
	fi
done < "$file_to_be_filtered"

# sort by facing order number
sort $result_file -o $result_file
# cut order number and first space, output the rest
cut -d " " -f 2- $result_file

rm -f $result_file
]]>
Die Suche nach der Fuzzy-Search https://sgaul.de/2013/06/30/die-suche-nach-der-fuzzy-search/ https://sgaul.de/2013/06/30/die-suche-nach-der-fuzzy-search/#comments Sun, 30 Jun 2013 12:11:22 +0000 https://sgaul.de/?p=2252 Die Suche nach der Fuzzy-Search weiterlesen]]> Unscharfes Suchen ist äußerst hilfreich. Wer länger mit Sublime oder vergleichbaren Editoren gearbeitet hat weiß es zu schätzen, dass er statt eines Datei-Dialogs Strg+p, „filibpost“ und Enter drückt, um etwa files/sql/libs/postgresql-lib.rb zu öffnen. Ich arbeite gerade an einem ähnlichen Hilfskommando, welches das Öffnen in Vim etwa mittels ff filibpost vim ermöglicht. Alles nicht so schwierig – bis auf die sogenannte „Fuzzy Search“.

Zunächst mein (nicht wirklich optimales) Testszenario:

$ tree
.
└── files
    └── sql
        └── libs
            ├── db2-lib.rb
            ├── mariadb-lib.rb
            ├── mysql-lib.rb
            └── postgresql-lib.rb

find + grep

Mit find . -type f werden alle Dateien des aktuellen Verzeichnisses und aller Unterverzeichnisse aufgelistet. Diese werden dann auf den Suchstring überprüft. Da Grep nicht „unscharf“ ist, wird der Suchstring „abc“ mittels $(echo "$1" | sed 's/./&.*/g') in „*a*b*c*“ umgewandelt.

function grep_search {
  fuzzy_search_string=$(echo "$1" | sed 's/./&.*/g')
  find . -type f | grep -i "$fuzzy_search_string"
}

Schwachstellen

Diese Lösung funktioniert und ist auch schnell genug, allerdings sind die Ergebnisse nicht gewichtet. Wenn ich nach „bilderfamilie“ suche, treffen auch sehr tiefe Hierarchien schnell zu. Und wenn sich mein gewünschtes Ergebnis zwischen hunderten Dateien wie ./Bilder/Icons/Faenza/c/22/media-optical-audio-new.png steckt, wird das Herauspicken aufwendig.

find + agrep -p

Dann habe ich über Agrep gelesen, was zunächst vielversprechend klang. Das Tool ist mächtig, aber auch entsprechend etwas komplizierter. Die Option -p war in den Man-Pages jedoch vielversprechend dokumentiert:

agrep -p DCS foo will match „Department of Computer Science.“

function agrep_search {
  find . -type f | agrep -pi $1
}

Schwachstellen

Um es kurz zu machen: Das Ergebnis ist das selbe wie bei dem selbst zusammengesetztem Grep.

find + agrep -By

Okay, Ziel verfehlt. Die „besten“ Treffer sind immer noch nicht oben. Aber dann:

Best match mode. When -B is specified and no exact matches are found, agrep will continue to search until the closest matches (i.e., the ones with minimum number of errors) are found…

Genau was ich will. Wird kein exakter Treffer gefunden, sucht man einen mit einem, dann mit zwei, dann mit drei Fehlern. Klingt gut.

Die Option -B kann nicht vom Stdin lesen (sie muss ja mehrfach durchgehen), daher die etwas andere Implementierung:

function agrep_by_search {
  find . -type f > $HOME/.agrepresults
  agrep -By -S8 -D8 -i $1 $HOME/.agrepresults
  rm $HOME/.agrepresults
}

Schwachstellen

Diese Methode findet nur die besten Lösungen, also die, die mit den wenigsten Fehlern passen. Passt die gesuchte Datei mit drei Fehlern, eine andere aber schon mit zwei, wird die gesuchte nicht angezeigt. Dies ließe sich beheben, in dem man die Funde aus der Zwischenspeicherdatei löscht und die Suche erneut startet, bis die Ergebnisliste mindestens x Einträge hat.

Ein größeres Problem ist aber, dass diese Suchmethode alle Fehlerarten zu berücksichtigen scheint: Einfügung, Ersetzung und Löschung. In einer unscharfen Suche sollten jedoch nur Einfügungen erlaubt sein. Das Heraufsetzen der Kosten von Deletion und Substitution mittels -S8 -D8 scheint leider keinen Unterschied zu machen. So zeigt die Suche nach „mlib“ im obigen Dateisystembeispiel auch Pfade an, die kein m enthalten (siehe folgende Tabelle).

Beispiel-Suchanfragen

grep mit sed ’s/./&.*/g‘ agrep -pi agrep -By
sql
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/postgresql-lib.rb
./files/sql/libs/db2-lib.rb
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/postgresql-lib.rb
./files/sql/libs/db2-lib.rb
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/postgresql-lib.rb
./files/sql/libs/db2-lib.rb
mlib
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/postgresql-lib.rb
./files/sql/libs/db2-lib.rb
sql-lib
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/postgresql-lib.rb
./files/sql/libs/db2-lib.rb
./files/sql/libs/mysql-lib.rb
./files/sql/libs/mariadb-lib.rb
./files/sql/libs/postgresql-lib.rb
./files/sql/libs/db2-lib.rb
./files/sql/libs/mysql-lib.rb
./files/sql/libs/postgresql-lib.rb

Die Suche geht also weiter…

]]>
https://sgaul.de/2013/06/30/die-suche-nach-der-fuzzy-search/feed/ 4
Dateien nach LOC auflisten https://sgaul.de/2013/05/07/dateien-nach-loc-auflisten/ Tue, 07 May 2013 16:14:49 +0000 https://sgaul.de/?p=2155 Dateien nach LOC auflisten weiterlesen]]> Das einfachste, aber manchmal durchaus ausreichende Maß für Komplexität sind die guten alten Lines of Code. Hier ein kleiner Schnipsel, um in der Bash alle Ruby-Dateien des aktuellen Verzeichnisses und aller Unterverzeichnisse aufzulisten:

find . -name '*.rb' -print0 | xargs -0 wc -l | sort -n

Das funktioniert natürlich mit jeder beliebigen Sprache. Einfach die Dateiendung im Find-Befehl anpassen.

]]>
Coffeescript nur nach Änderung kompilieren https://sgaul.de/2013/04/13/coffeescript-nur-bei-anderung-kompilieren/ Sat, 13 Apr 2013 12:11:23 +0000 https://sgaul.de/?p=2110 Mein Build- und Entwicklungshilfe-Script für Node-Projekte kann bereits das Wesentliche: Coffeescript kompilieren, Tests ausführen, einen Server starten und beenden sowie das Dateisystem überwachen, um die anderen Aufgaben nach jedem Speichern automatisch auszuführen. Die schnell geschriebene Build-Task kompilierte bisher jede Coffeescript-Datei, die sie finden konnte. Um nach dem Speichern möglichst schnell Testergebnisse zu erhalten, sollte nur neu gebaut werden, was sich auch geändert hat.

Um dies möglichst einfach zu erreichen, schlage ich ein Source- (z.B. project/src) und ein Zielverzeichnis (z.B. project/build) vor. In src kann man weitere Grobstrukturen wie main und tests festlegen. So fließen meine zuvor definierten Tasks build_project und build_tests zu einem einzigen build zusammen.

Unterschiede erkennen? Rsync!

Um möglichst effizient etablierte Technologien verwenden zu können, werden vor dem Kompilieren alle Quelldateien ins Buildverzeichnis kopiert. Dank dieser Vereinfachung lässt sich Rsync verwenden, welches nur wirklich geänderte Dateien synchronisiert und ihren Pfad ausgibt. Nach dem Sync muss die Liste der Änderungen nur noch nach Coffee-Dateien durchsucht und diese kompiliert werden. Der gesamte Vorgang sieht dann folgendermaßen aus:

build() {
    changes=( "$(rsync -av "$source_directory/" "$build_directory")" )
    for filepath in $changes; do
      if [[ $filepath == *.coffee ]]; then
        $compiler --compile "$build_directory/$filepath"
      fi
    done
}

Einziger Schönheitsfehler: Im Buildverzeichnis liegen auch die Coffeescript-Dateien neben ihren Javascript-Äquivalenten, obwohl sie nicht gebraucht werden. Diese dürfen im Buildprozess nicht entfernt werden, da ihre Löschung den Rsync-Ansatz ad absurdum führt. Während der Entwicklung stören sie auch nicht. Für das produktive Deployment empfiehlt sich jedoch eine zusätzliche Task, die das Buildverzeichnis zuvor bereinigt.

Implementierte Build-Tasks

Das Script ist mittlerweile vollständig genug, um die Node-Entwicklung zu begleiten. Folgende Tasks stehen zur Verfügung:

Task Beschreibung
build Build project and tests.
test Build project and run all tests.
Equivalent to „build.sh build run-tests“.
run-tests Run all tests.
server Stop running server (if existing), build project and start server.
Equivalent to „build.sh stop-server build start-server“.
restart-server Stop running server (if existing) and start server.
Equivalent to „build.sh stop-server start-server“.
start-server Start server.
stop-server Stop running server (if existing).
watch Start infinite loop triggering succeeding tasks whenever a file changes
in project directory. Use only once.
dev Build everything, restart server, run tests and repeat whenever a file
changes. Equivalent to „build.sh test watch test“.

Node-Build-Script auf Github

]]>
Server im Build-Prozess starten und stoppen https://sgaul.de/2013/04/09/server-im-build-prozess-starten-und-stoppen/ Tue, 09 Apr 2013 13:41:32 +0000 https://sgaul.de/?p=2094 Server im Build-Prozess starten und stoppen weiterlesen]]> Entwickelt man eine Server-Anwendung, so sollte das verwendete Echtzeit-Build-Script auch in dieser Lage sein, diesen zu starten und zu beenden. So muss der Entwickler nach dem Speichern im Browser nur noch F5 drücken, um die Änderungen zu sehen (das völlig unzumutbare F5-Drücken wird später auch entfallen).
Einen vorhandenen Node-Server von der Bash zu starten ist keine Raketenwissenschaft. Man sollte sich nur gleich beim Start merken, welche PID der neue Prozess bekommen hat, so dass man diesen gezielt beenden kann. Da das Script zwischenzeitlich beendet werden kann, wird die PID in einer Datei hinterlegt.

start_server() {
    echo "Start server..."
    $application_launcher "$build_directory/run-server.js" &
    echo $! > .serverpid
}

stop_server() {
    if [ -f ".serverpid" ]; then
        local server_pid=$(head -n 1 .serverpid)
        rm .serverpid
        if [[ $server_pid > 0 ]]; then
            echo "Stop server..."
            kill $server_pid
            server_pid=0
        fi
    fi
}

Es erscheint hier etwas unschön, stop_server() im Falle einer nicht gegebenen PID einfach nichts machen zu lassen. Die Bedingung sollte an der Aufrufstelle stehen oder der Funktionsname auf das nur bedingte Stoppen hinweisen. Da ich aber kurze und einfache Funktionen für ein Kommandozeilentool suche, halte ich diese Variante für pragmatisch und akzeptabel.

Im Argument-auf-Funktionsmapping des Scripts werden noch zwei oft genutzte Zusammenfassungen ergänzt:

process_tasks() {
    while [[ ${#tasks[@]} > 0 ]]; do
        local task=${tasks[0]}
        unset tasks[0]
        tasks=("${tasks[@]}")
        case "$task" in
            # ...
            'server')
                stop_server
                build_project
                start_server
            'restart-server')
                stop_server
                start_server
                ;;
            'start-server')
                start_server
                ;;
            'stop-server')
                stop_server
                ;;
            # ...
        esac
    done
}

Node-Build-Script auf Github

]]>
Bash-Build-Script mit Desktop-Nachrichten und Dateiüberwachung https://sgaul.de/2013/04/07/bash-build-script-mit-desktop-nachrichten-und-dateiuberwachung/ Sun, 07 Apr 2013 12:43:30 +0000 https://sgaul.de/?p=2079 Bash-Build-Script mit Desktop-Nachrichten und Dateiüberwachung weiterlesen]]> Ich habe mich die letzte Zeit viel mit Grunt herumgeschlagen. Grunt ist ein umfangreiches und mächtiges Build-Werkzeug für Node, dennoch hatte ich stets das Gefühl, dass die Konfiguration umständlicher als der wirkliche Nutzen ist. Ich habe daher mit einem Build-Script auf Bash-Basis begonnen, welches vom Nutzer spezifizierte Aufgaben nacheinander ausführt. Diese frühe Version unterstützt Coffeescript-Kompilierung, Testausführung und Dateisystemüberwachung, welche die anderen Tasks bei jeder Änderung automatisch ausführt. Nach dem Speichern bekommt man so stets die direkte Rückmeldung, ob die Unittests erfolgreich waren.

Bash-Build-Script für Node
Bash-Build-Script für Node

Boilerplate: Task-Abarbeitung

Auszuführende Aufgaben werden aus den Argumenten in ein Array tasks übernommen. Jede atomare Task Aufgabe hat eine Funktion (im Beispiel mytask()), welche die entsprechenden Operationen ausführt.

Die Abarbeitung selbst ist in process_tasks() definiert, was auch den Startpunkt des Scripts darstellt. Hier wird über das Aufgabenarray iteriert und anhand des Names die passende Funktion ausgeführt.

tasks=("${@:1}")

mytask() {
    # ...
}

# ...

process_tasks() {
    while [[ ${#tasks[@]} > 0 ]]; do
        local task=${tasks[0]}
        unset tasks[0]
        tasks=("${tasks[@]}")
        case "$task" in
            'mytask')
                mytask
                ;;
            # ...
        esac
    done
}

process_tasks

Testausführung mit Desktop-Benachrichtigungen

Die Funktion notify nimmt eine Nachricht und einen Typ (Erfolg oder Fehler) und stellt sie auf dem Desktop dar. Entsprechend des Typs wird ein Icon gewählt. Dieser Teil ist leider plattformabhängig. Für Linux habe ich notify-send gewählt.

Vom Unittest-Script wird hier in der Funktion run_tests erwartet, dass bei fehlgeschlagenen Tests auch ein Fehlercode, also ungleich 0, zurückgegeben wird. Ist dies nicht der Fall, muss hier die Ausgabe überwacht werden.

notify() {

    local message=$1      # the message to be sent
    local message_type=$2 # "error" || "success" || undefined

    if hash "notify-send" 2> /dev/null; then
        local icon="face-plain"
        if [[ "$message_type" == "error" ]]; then
            icon="error"
        elif [[ "$message_type" == "success" ]]; then
            icon="face-smile-big"
        fi
        notify-send -u low -t 500 -i "$icon" "$message"
    fi
}

run_tests() {
    $application_launcher $build_directory/test/precompiler.spec.js
    if [[ $? > 0 ]]; then
        notify "One or more tests failed!" "error"
    else
        notify "All tests passed." "success"
    fi
}

Watch: Dateisystem überwachen

Anwendung

Die Watchtask führt alle ihr nachfolgenden Tasks immer wieder aus, wenn sich im überwachten Verzeichnis etwas ändert.

./build.sh t1 t2 watch t3 t4

Die Aufgaben t1 und t2 werden direkt ausgeführt, ab watch wird gewartet. Mit jeder Änderung werden t3 und t4 ausgeführt. Ein sinnvolles Beispiel wäre somit:

./build.sh test watch test

Die Task test beinhaltet auch den Build. Der Aufruf führt die Tests zu Beginn und nach jeder Änderung aus.

Umsetzung

Das Dateisystem wird von inotifywait auf Änderungen überwacht. Es blockiert die Abarbeitung, bis eine Änderung vorgenommen wird. Hiernach werden die restlichen Tasks aus der Liste ausgeführt. Damit das Script dann nicht terminiert, wird die Taskliste um eine weitere Watch-Task und sich selbst ergänzt. So entsteht eine Endlosschleife.

watch() {
    tasks=("${tasks[@]}" "watch" "${tasks[@]}")
    inotifywait -rqe close_write,moved_to,create ./lib ./test
}

Code auf Github

Das Script kann als Vorlage für eigene Build-Scripts verwendet werden. Es steht unter der üblichen Für-alle-außer-Microsoft-und-Apple-Lizenz und ist auf Github zu finden:

Node-Build-Script auf Github

]]>
Pfad und Name eines Bash-Skripts https://sgaul.de/2013/03/26/pfad-und-name-eines-bash-skripts/ Tue, 26 Mar 2013 19:41:11 +0000 https://sgaul.de/?p=2043 Pfad und Name eines Bash-Skripts weiterlesen]]> Oft muss ein Bash-Script einige Sachen von sich selbst wissen, um zum Beispiel das CWD sicherzustellen oder sinnvolle Ausgaben zu erzeugen. Die folgenden drei Befehle befüllen drei Variablen mit dem absoluten Verzeichnispfad, mit dem Dateinamen und dem vollständigen Pfad zur aktuellen Script-Datei:

#!/bin/bash

# /home/user
scriptdir=$(cd $(dirname $0); pwd -P)

# test.sh
scriptname=$(basename $0)

# /home/user/test.sh
scriptpath=$(cd $(dirname $0); pwd -P)/$(basename $0)
]]>