Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder einen alternativen Browser verwenden.
Ja. :-)
"Bash" hat viele Nachteile. das fängt schon allein damit an, das man keine vernünftigen Datentypen und Datenstrukturen hat und alles immer aufwendig parsen muss, wenn man von einem Programm etwas zurück bekommt (und dann noch hoffen muss, das die Ausgabe sich nicht in der nächsten Version ändert oder das man auch im Blick hat, wie LANG gesetzt ist usw.)
Mit Skriptsprachen a-la Python oder auch Ruby und wie sie alle heißen ist man i.d.R. besser dran.
Auf der Haben-Seite hat man allerdings, das man die Tools und auch auf die Weise benutzen kann, die man vom interaktiven Gebrauch der Shell gewohnt ist. Außerdem ist die Shell immer da und auf dem System muss nicht unbedingt ein Python sein und in manchen Fällen ist es doof, wenn man extra deswegen was installieren müsste.
Und insbesondere für kleine Sachen tut es ja oftmals auch ein Shell-Skript. Insbesondere bei diesen typischen, kleinen Wergwerf-Skripten, wo man nur mal schnell eben was machen will.
Insofern hängt es auch immer ein bisschen von der Situation und dem was man erreichen will ab.
btw.: Wenn ich Shellskripte schreibe, dann setze ich nicht mal die bash voraus, sondern nur eine POSIX-konforme Shell. Das ist dann noch mal eine Ecke "schärfer". :-)
Für größere Sachen stimme ich ja uneingeschränkt zu, zum Beispiel Python zu verwenden, aber nicht bei einem Dreizeiler (auch ... wenn man mit drei Zeilen natürlich auch schon viel bewerkstelligen kann).
Ist das überhaupt semantisch identisch? Bash ist schon eine Weile her, aber bei der zweiten Version wird doch der Rückgabewert von echo 'a' >> b.txt verwendet, bei der ersten nicht.
Vielleicht, weil ich nicht extra eine Anwendung schreiben will, die eine andere Anwendung installiert und konfiguriert?
Ein Interpreter wird in jedem Fall gestartet, entweder ein Bash Interpreter oder ein Python Interpreter. Ich finde, der Thread hier ist ein Indiz dafür, dass sich Ruby oder Python auch für kleinere Skripte lohnen.
"Easy to write, hard to read" ist gut für Dinge die nicht persistent sind, z.B. vim Befehle oder spontanes suchen und ersetzen mit regex oder einen Befehl über das cli ausführen. Code der abgespeichert ist sollte aber grundsätzlich "easy to read" sein.
Ich denke, du verwechselst grad interpretieren und kompilieren von Code. Ich habe jedenfalls noch nie gehört, dass sich jemand fragt, wie sich interpretierter Code auf CPU Instruktionen abbildet.
Ja, ich denke schon, auch wenn im ersteren Fall ; statt && verwendet wird... weil ja das set -e verwendet wird, und bei einem Fehler beides abbrechen würde (durch das set -e und das kurzschließende || und &&).
Man muss nur bei der Präzedenz aufpassen, weil sich diese von C unterscheidet... (alle Operatoren haben die gleiche Präzedenz) vermutlich, weil das ganze Teil älter als C ist. 😅
Wie kommst Du zu dieser Fehlannahme? Du kannst beispielsweise in C logisch die Reihenfolgen von & bzw. | vertauschen, was für Dich keinen Unterschied macht, aber es kann intern sehr wohl was ganz anderes und sogar nachteiligeres übersetzt werden.
Natürlich hat das Relevanz, wenn ein || wie if else im Endeffekt dann durch den Compiler intern gleich behandelt wird. Anders siehts es aus, wenn ein || logisch und in der bash interpretiert zwar kürzer erscheint, aber dann durch eine aufwendigere Konstruktion ersetzt wird als eine if else Anweisung. Dazu müßte man aber im Maschinencode debuggen, oder vermutlich einfacher, sich mal den Quellcode der Bash in Linux mal genauer anschauen und debuggen, wie das jeweils übersetzt und dann interpretiert wird. Oder bash selbst macht aus || auch intern nur ein schönes if else beim interpretieren.
Wie kommst Du zu dieser Fehlannahme? Du kannst beispielsweise in C logisch die Reihenfolgen von & bzw. | vertauschen, was für Dich keinen Unterschied macht, aber es kann intern sehr wohl was ganz anderes und sogar nachteiligeres übersetzt werden.
Ich spreche von interpretierten Code. C wird in aller Regel kompiliert, nicht interpretiert. Davon abgesehen macht man so gut wie gar nicht mehr solche Mikro-Optimierungen. Einmal, weil Compiler mitlerweile deutlich schlauer sind als noch vor 30 Jahren und zweitens weil der potentielle Performancegewinn in aller Regel keine Rolle spielt.
nutrix schrieb:
Anders siehts es aus, wenn ein || logisch und in der bash interpretiert zwar kürzer erscheint, aber dann durch eine aufwendigere Konstruktion ersetzt wird als eine if else Anweisung.
Aha und welche Relevanz könnte das rein theoretisch haben? Niemand lässt einen Interpreter über Code laufen, wenn es relevant ist, welche konkreten CPU Instruktionen letztendlich verwendet werden.
@nutrix Ja und wie schon gesagt ist es komplett irrelevant, welche konkreten CPU Instruktionen dann ausgeführt werden und ob Ausdruck X einer interpretierten Sprache (z.B. Bash) eine leicht andere Sequenz erzeugt als Ausdruck Y.
Ist es nicht, und wenn Du es mir nicht glauben magst, bau einfach mal selbst einen solchen Interpreter, und dann wirst Du es hoffentlich beim Debuggen selbst sehen und verstehen. Wenn Du in der Interpretersprache ein IF verwendest, was sich dann entsprechend 1:1 in einen IF in der Sprache des geschriebenen Interpreters wie C oder anderes umsetzten läßt, was glaubst Du wohl, was ein Entwickler machen wird? Keiner erfindet das Rad neu, wenn es nicht zwingend ist. Programmierer sind doch grundsätzlich mal faul, oder nicht? 😉 Ein Interpreter ist, wenns entsprechend nahe programmiert wurde, auch "nur" ein Wrapper, der die Funktionen abbildet bzw. durchreicht oder (leicht) erweitert. Aber ja, wenn natürlich ein Entwickler hier ein eigenes IF in beispielsweise C bauen würde, dann würde es natürlich anders umgesetzt werden und wäre dann tatsächlich zur Laufzeit in der CPU als Vergleich irrelevant!
Egal wie oft Du es noch behauptest, Deine Annahme ist so nicht richtig. Am Ende führt eine CPU das alles aus, in Maschinencode, und nicht in Interpretersprache. Und das kann sehr wohl auch im Interpreter selbst (je nach Anspruch, Aufwand und Komplexität) machinenoptimiert vorbereitet bzw. abgebildet werden.
Aber wir sind viel zu OT und nützt für dieses Problem nichts weiter.
Ergänzung ()
BeBur schrieb:
Mir ist generell aber auch ein Rätsel, wieso man überhaupt freiwillig Bash verwendet anstelle einer modernen, weniger qualvollen Sprache.
Weil Bash an sich keine Sprache ist, sondern ein Batchprogramm. 😉 Damit löst man eben andere Probleme als mit einer Programmiersprache. Wer Server und Workstations im Unix/Linux Umfeld betreiben will, will sie effektiv verwalten können, und dafür sind Batchsprachen optimiert und gut dafür.
Und @CybogBeta möchte ja genau das, wenn ich es richtig verstehe.
Natürlich sind Batchsprachen (auch wenn sie es teilweise theoretisch könnten) nicht dafür geeignet, Anwendungen mit komplexen Problemlösungen zu ersetzen. Aber umgekehrt willst Du doch so schöne Sachen wie ein Cronjob mit Backup und Syncs über Nacht und anderes nicht aufwendig selbst "programmieren" wollen.
. Wenn Du in der Interpretersprache ein IF verwendest, was sich dann entsprechend 1:1 in einen IF in der Sprache des geschriebenen Interpreters wie C oder anderes umsetzten läßt, was glaubst Du wohl, was ein Entwickler machen wird?
Das kann man dennoch nicht vergleichen. In C kann der Compiler ggf. bestimmte Annahmen treffen. Zum Beispiel wenn Du da ein Zahl verarbeitest, dann weiß der Compiler das ist eine Zahl und braucht keine Typenwandlung zu machen. Wenns ein Integer ist kann er sogar spezialisierte (und damit schnellere) Instruktionen verwenden.
usw. usw.
Das konntest Du theoretisch in der Bash auch machen, aber deine Optimierungsversuche würden mehr Zeit kosten, als Du in den meisten Fällen gewinnen kannst (das kann man auch schön bei diesen ganzen Just-in-Time-Compilern bestaunen, die erst mal "warmlaufen" müssen um wirklich effektiv zu sein).
Das kommt schon deshalb nicht hin, weil die Bash das Skript auch parsen muss usw. (all das, was bei C der Compiler mit macht und deshalb gar nicht im Ergebniscode drin ist). Schon allein das kostet soviel Code (und damit Zeit), das die eigentliche Nutz-Operation gar nicht mehr ins Gewicht fällt.
Die Behauptung (so sie denn so gemeint war), das bei C und bei der Bash hinten letztlich ungefähr selbe Maschinencode rauskommt ist nicht haltbar.
Es geht direkt um die Funktion, die man direkt durchreichen kann, nicht um alles andere darum herum. Das der Code interpretiert dann durch die Parsierung langsamer und anders läuft ist klar.
Ich formuliere es anders: Es kann durchaus sein, daß ein || umständlicher und langsamer funktioniert als ein IF ELSE an der Stelle, oder auch schneller.
Das kommt schon deshalb nicht hin, weil die Bash das Skript auch parsen muss usw. (all das, was bei C der Compiler mit macht und deshalb gar nicht im Ergebniscode drin ist). Schon allein das kostet soviel Code (und damit Zeit), das die eigentliche Nutz-Operation gar nicht mehr ins Gewicht fällt.
Die Behauptung (so sie denn so gemeint war), das bei C und bei der Bash hinten letztlich ungefähr selbe Maschinencode rauskommt ist nicht haltbar.
Ich glaube, das stimmt nicht ganz, da auch ein Bash- (oder Sh-) Script einmal ganz gelesen wird, bevor es interpretiert und ausgeführt wird. Also erfolgt die Ausführung nicht Zeile für Zeile (wie sollte dies bei if else Blöcken auch anders möglich sein?).
Insofern wäre es schon interessant, welcher Bytecode, also welche CPU-Instruktionen da letztlich herauspurzeln und ausgeführt werden - bzw., welche Optimierungen der Interpreter vornimmt.
---
Ich habe mal ein etwas komplexeres Beispiel für euch:
Java:
private static final Predicate<Path> notSample =
p -> !p.toFile().getPath().toLowerCase(Locale.ROOT).contains("sample");
private static final Predicate<Path> isDirectory = p -> p.toFile().isDirectory();
private static final Predicate<Path> isFile = p -> p.toFile().isFile();
private static final Predicate<Path> isRar = p -> p.toFile().getName().endsWith(".rar");
private static final Predicate<Path> isImg = p -> p.toFile().getName().endsWith(".jpg");
private static void unrarAndMove(String fullSource, String fullDestination) throws IOException {
File temp_sf = new File(fullSource);
File temp_df = new File(fullDestination);
if (!temp_sf.isDirectory() || !temp_df.isDirectory()) {
System.out.println("Invalid source or destination");
System.exit(0);
}
if (!temp_sf.isAbsolute()) {
temp_sf = temp_sf.getAbsoluteFile();
}
if (!temp_df.isAbsolute()) {
temp_df = temp_df.getAbsoluteFile();
}
File sf = temp_sf;
File df = temp_df;
System.out.println("Source: " + sf);
System.out.println("Destination: " + df);
try (Stream<Path> walk = Files.walk(sf.toPath())) {
walk.sorted()
.filter(notSample)
.filter(isDirectory)
.forEach(
path -> {
String middle = path.toString().replace(sf.getParent(), "");
File newFolder = new File(df + middle);
if (!newFolder.exists()) {
System.out.println("Creating folder: " + newFolder);
System.out.println(newFolder.mkdirs());
}
});
}
try (Stream<Path> walk = Files.walk(sf.toPath())) {
walk.sorted()
.filter(notSample)
.filter(isFile)
.filter(isRar)
.forEach(path -> call(path.toFile().getParentFile(), "unar " + path.toFile().getName()));
}
try (Stream<Path> walk = Files.walk(sf.toPath())) {
walk.sorted()
.filter(notSample)
.filter(isFile)
.filter(isImg)
.forEach(
path -> {
String middle = path.toString().replace(sf.getParent(), "");
File newFile = new File(df + middle);
call(
path.toFile().getParentFile(),
"mv -v " + path.toFile().getName() + " " + newFile);
});
}
}
private static void call(File dir, String cmd) {
try {
System.out.println("Executing: " + cmd + " in " + dir);
ProcessBuilder pb = new ProcessBuilder("bash", "-c", cmd);
pb.directory(dir);
Process pro = pb.start();
try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(pro.getInputStream(), Charset.defaultCharset()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
System.out.println("Exit code: " + pro.waitFor());
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
Vereinfacht gesagt, sollen Archive entpackt und verschoben werden. Dabei soll die Quellverzeichnisstruktur auf die Zielverzeichnisstruktur abgebildet werden, und "Sample"-Verzeichnisse sollen ignoriert werden.
Vermutlich wird das mit einem Shell-Script nicht so einfach möglich sein.
Na geht schon. Man lässt sich halt vom rar-Kommando den Archivinhalt anzeigen und parst und iteriert sich da irgendwie durch. Man kann schon in Shellskript einiges machen. Und es gibt durchaus auch umfangreichere Shellskripte. Zum Beispiel der FreeBSD-Installer oder auch der Updater ist im wesentlichen Shell-Skript.
Und ganz oft muss Shellskript ja auch gar nicht die eigentliche Arbeit leisten. Das ist ja oft eher glue-code um Programme zu verbinden.
nutrix schrieb:
Es geht direkt um die Funktion, die man direkt durchreichen kann, nicht um alles andere darum herum.
Ja. Aber selbst da.
Mal ein ganz triviales Beispiel:
CPUs haben häufig spezielle Instruktionen um gegen 0 zu vergleichen. Das kommt öfter mal vor und deshalb lohnt es sich auch, dafür eine eigene Instruktion zu haben die dann auch besonders schnell ausgeführt wird.
In der Bash wird es aber so bei der Ausführung von Shell-Skript nicht verwendet werden weil die Bash ja vorher (zur Compile-Zeit) nicht weiß, ob das Skript am Ende mit Null vergleicht oder nicht. Da wird dann auf der Maschinencode-Ebene also letztlich ein generischer Vergleich laufen. Theoretisch könnte das die Bash vorher prüfen und dementsprechend einen anderen Codepfad wählen. Macht aber keiner, weil allein die Prüfung mehr Zeit kostet, als das Du sie durch die optimierte Maschinencode-Instruktion rausholen kannst.
Mal ein ganz triviales Beispiel:
CPUs haben häufig spezielle Instruktionen um gegen 0 zu vergleichen. Das kommt öfter mal vor und deshalb lohnt es sich auch, dafür eine eigene Instruktion zu haben die dann auch besonders schnell ausgeführt wird.
In der Bash wird es aber so bei der Ausführung von Shell-Skript nicht verwendet werden weil die Bash ja vorher (zur Compile-Zeit) nicht weiß, ob das Skript am Ende mit Null vergleicht oder nicht. Da wird dann auf der Maschinencode-Ebene also letztlich ein generischer Vergleich laufen.