Ich versuche, den effizientesten Weg zu finden, um bestimmte Werte zu durchlaufen, bei denen es sich um eine konsistente Anzahl von Werten handelt, die in einer durch Leerzeichen getrennten Liste von Wörtern voneinander entfernt sind (ich möchte kein Array verwenden). Beispielsweise,
list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
Ich möchte also in der Lage sein, nur die Liste zu durchlaufen und nur auf 1,5,6,9 und 15 zuzugreifen.
BEARBEITEN: Ich hätte klarstellen müssen, dass die Werte, die ich aus der Liste abrufen möchte, nicht im Format vom Rest der Liste abweichen müssen. Was sie besonders macht, ist allein ihre Position in der Liste (in diesem Fall Position 1,4,7 ...). Die Liste könnte also lauten,1 2 3 5 9 8 6 90 84 9 3 2 15 75 55
aber ich möchte immer noch die gleichen Nummern. Außerdem möchte ich in der Lage sein, dies zu tun, vorausgesetzt, ich kenne die Länge der Liste nicht.
Die Methoden, an die ich bisher gedacht habe, sind:
Methode 1
set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
if [ "${@:count:1}" -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
done
Methode 2
set list
found=false
find=9
while [ $# ne 0 ]; do
if [ $1 -eq $find ]; then
found=true
break
fi
shift 3
done
Methode 3 Ich bin mir ziemlich sicher, dass Piping die schlechteste Option ist, aber ich habe aus Neugier versucht, eine Methode zu finden, bei der set nicht verwendet wird.
found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
if [ $num -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
num=`echo $list | cut -d ' ' -f$count`
done
Was wäre also am effizientesten, oder fehlt mir eine einfachere Methode?
quelle
Antworten:
Ziemlich einfach mit
awk
. Damit erhalten Sie den Wert jedes vierten Feldes für Eingaben beliebiger Länge:Dies funktioniert, indem Sie integrierte
awk
Variablen wieNF
(die Anzahl der Felder im Datensatz) nutzen und einige einfachefor
Schleifen ausführen, um die Felder zu durchlaufen, um die gewünschten zu erhalten, ohne vorher wissen zu müssen, wie viele es geben wird.Oder, wenn Sie in der Tat nur die in Ihrem Beispiel angegebenen Felder benötigen:
Bei der Frage nach der Effizienz ist es am einfachsten, diese oder jede Ihrer anderen Methoden zu testen und
time
zu zeigen, wie lange dies dauert. Sie können auch Tools verwenden, umstrace
zu sehen, wie die Systemaufrufe ablaufen. Verwendung vontime
sieht aus wie:Sie können diese Ausgabe zwischen verschiedenen Methoden vergleichen, um festzustellen, welche zeitlich am effizientesten ist. Andere Tools können für andere Effizienzmetriken verwendet werden.
quelle
echo
vs<<<
, "identisch" ist ein zu starkes Wort. Man könnte sagen, dasstuff <<< "$list"
ist fast identisch mitprintf "%s\n" "$list" | stuff
. In Bezug aufecho
vsprintf
,<<<
fügt am Ende eine neue Zeile hinzu. Dies ist vergleichbar mit dem$()
Entfernen eines Zeilenumbruchs vom Ende. Dies liegt daran, dass Zeilen durch Zeilenumbrüche abgeschlossen werden.<<<
Füttert einen Ausdruck als Zeile, sodass er durch eine neue Zeile abgeschlossen werden muss."$()"
Nimmt Zeilen und stellt sie als Argument zur Verfügung, daher ist es sinnvoll, sie zu konvertieren, indem Sie den abschließenden Zeilenumbruch entfernen.awk
handelt sich um eine eigenständige Binärdatei, die gestartet werden muss. Im Gegensatz zu Perl oder speziell Python startet der awk-Interpreter schnell (immer noch der übliche dynamische Linker-Aufwand für einige Systemaufrufe, aber awk verwendet nur libc / libm und libdl. ZBstrace
zum Auschecken von Systemaufrufen beim awk-Start). . Viele Shells (wie Bash) sind ziemlich langsam, daher kann das Starten eines awk-Prozesses schneller sein als das Überlaufen von Tokens in einer Liste mit integrierten Shell-Funktionen, selbst bei kleinen Listengrößen. Und manchmal können Sie ein#!/usr/bin/awk
Skript anstelle eines#!/bin/sh
Skripts schreiben .Erste Regel der Softwareoptimierung: Nicht .
Solange Sie nicht wissen, wie schnell das Programm ist, müssen Sie nicht darüber nachdenken, wie schnell es ist. Wenn Ihre Liste ungefähr so lang oder nur ~ 100-1000 Elemente lang ist, werden Sie wahrscheinlich nicht einmal bemerken, wie lange es dauert. Es besteht die Möglichkeit, dass Sie mehr Zeit damit verbringen, über die Optimierung nachzudenken, als der Unterschied wäre.
Zweite Regel: Messen .
Das ist der sichere Weg, um herauszufinden, und derjenige, der Antworten für Ihr System gibt. Besonders bei Muscheln gibt es so viele, und sie sind nicht alle identisch. Eine Antwort für eine Shell trifft möglicherweise nicht auf Ihre zu.
In größeren Programmen gilt auch hier die Profilerstellung. Der langsamste Teil ist möglicherweise nicht der, von dem Sie glauben, dass er es ist.
Drittens die erste Regel der Shell-Skriptoptimierung: Verwenden Sie die Shell nicht .
Ja wirklich. Viele Shells sind nicht besonders schnell (da das Starten externer Programme nicht unbedingt erforderlich ist) und analysieren die Zeilen des Quellcodes möglicherweise jedes Mal neu.
Verwenden Sie stattdessen etwas wie awk oder Perl. In einem trivialen Mikro-Benchmark war ich
awk
beim Ausführen einer einfachen Schleife (ohne E / A) Dutzende Male schneller als jede herkömmliche Shell.Wenn Sie jedoch die Shell verwenden, verwenden Sie die integrierten Funktionen der Shell anstelle von externen Befehlen. Hier verwenden Sie,
expr
was nicht in Shells integriert ist, die ich auf meinem System gefunden habe, sondern das durch eine Standard-Arithmetik-Erweiterung ersetzt werden kann. ZBi=$((i+1))
statti=$(expr $i + 1)
zu inkrementiereni
. Ihre Verwendung voncut
im letzten Beispiel kann auch durch Standardparametererweiterungen ersetzt werden.Siehe auch: Warum wird die Verwendung einer Shell-Schleife zum Verarbeiten von Text als unangemessen angesehen?
Die Schritte 1 und 2 sollten auf Ihre Frage zutreffen.
quelle
awk
Loops notwendigerweise besser oder schlechter sind als Shell-Loops. Es ist so, dass die Shell wirklich gut darin ist , Befehle auszuführen und Eingaben und Ausgaben zu und von Prozessen zu leiten, und ehrlich gesagt ziemlich klobig bei allem anderen. während Werkzeuge wieawk
sind fantastisch bei der Verarbeitung von Textdaten, weil das, was Muscheln und Werkzeuge wieawk
für (jeweils) in erster Linie gemacht werden .dash
als mitgawk
unddash
waren die schnellste Shell, die ich getestet habe ...dash
undbusybox
nicht unterstützen(( .. ))
- ich denke, es ist eine nicht standardmäßige Erweiterung.++
wird auch ausdrücklich als nicht erforderlich erwähnt, soweit ich das beurteilen kanni=$((i+1))
oder: $(( i += 1))
die sicher sind.Ich werde in dieser Antwort nur einige allgemeine Ratschläge geben und keine Benchmarks. Benchmarks sind die einzige Möglichkeit, Fragen zur Leistung zuverlässig zu beantworten. Da Sie jedoch nicht angeben, wie viele Daten Sie bearbeiten und wie oft Sie diesen Vorgang ausführen, gibt es keine Möglichkeit, einen nützlichen Benchmark durchzuführen. Was für 10 Artikel effizienter ist und was für 1000000 Artikel effizienter ist, ist oft nicht dasselbe.
Generell ist das Aufrufen von externen Befehlen teurer als das Ausführen von reinen Shell-Konstrukten, sofern der reine Shell-Code keine Schleife enthält. Andererseits ist eine Shell-Schleife, die über eine große Zeichenfolge oder eine große Anzahl von Zeichenfolgen iteriert, wahrscheinlich langsamer als ein Aufruf eines Spezialwerkzeugs. Beispielsweise kann das Aufrufen einer Schleife
cut
in der Praxis sehr langsam sein. Wenn Sie jedoch eine Möglichkeit finden, das Ganze mit einem einzigencut
Aufruf zu erledigen , ist dies wahrscheinlich schneller als mit der String-Manipulation in der Shell.Beachten Sie, dass der Grenzwert zwischen den Systemen sehr unterschiedlich sein kann. Dies kann vom Kernel, der Konfiguration des Kernel-Schedulers, dem Dateisystem mit den externen ausführbaren Dateien, dem aktuellen CPU- und Speicherdruck und vielen anderen Faktoren abhängen.
Rufen Sie nicht
expr
an, um zu rechnen, wenn Sie sich überhaupt um die Leistung sorgen. Rufen Sieexpr
in der Tat gar nicht zum Rechnen auf. Shells verfügen über eine integrierte Arithmetik, die klarer und schneller ist als das Aufrufenexpr
.Sie scheinen bash zu verwenden, da Sie bash-Konstrukte verwenden, die in sh nicht existieren. Warum um alles in der Welt würden Sie kein Array verwenden? Ein Array ist die natürlichste und wahrscheinlich auch die schnellste Lösung. Beachten Sie, dass Array-Indizes bei 0 beginnen.
Ihr Skript ist möglicherweise schneller, wenn Sie sh verwenden, wenn Ihr System
sh
statt bash dash oder ksh as hat . Wenn Sie sh verwenden, erhalten Sie keine benannten Arrays, aber Sie erhalten immer noch einen der Positionsparameter für das Array, die Sie festlegen könnenset
. Um auf ein Element an einer Position zuzugreifen, die erst zur Laufzeit bekannt ist, müssen Sie verwendeneval
(achten Sie darauf, dass Sie die Dinge richtig zitieren!).Wenn Sie immer nur einmal auf das Array zugreifen möchten und von links nach rechts gehen (einige Werte überspringen), können Sie
shift
anstelle von Variablen Indizes verwenden.Welcher Ansatz schneller ist, hängt von der Shell und der Anzahl der Elemente ab.
Eine andere Möglichkeit ist die Verwendung der Zeichenfolgenverarbeitung. Es hat den Vorteil, dass die Positionsparameter nicht verwendet werden, sodass Sie sie für andere Zwecke verwenden können. Bei großen Datenmengen ist dies langsamer, bei kleinen Datenmengen macht sich dies jedoch kaum bemerkbar.
quelle
shift && shift && shift
mitshift 3
in Ihr drittes Beispiel - es sei denn , die Shell Sie verwenden sie nicht unterstützt.shift 3
würde scheitern, wenn es zu wenige verbleibende Argumente gäbe. Du brauchst so etwas wieif [ $# -gt 3 ]; then shift 3; else set --; fi
awk
ist eine gute Wahl, wenn Sie die gesamte Verarbeitung innerhalb des Awk-Skripts durchführen können. Andernfalls leiten Sie die Awk-Ausgabe einfach an andere Dienstprogramme weiter, wodurch der Leistungszuwachs von zunichte gemacht wirdawk
.bash
Die Iteration über ein Array ist auch großartig, wenn Sie Ihre gesamte Liste in das Array einpassen können (was bei modernen Shells wahrscheinlich eine Garantie ist) und die Syntaxgymnastik des Arrays nichts ausmacht.Ein Pipeline-Ansatz:
Woher:
xargs
gruppiert die durch Leerzeichen getrennte Liste in Dreiergruppen, wobei jede neue Zeile getrennt istwhile read
verbraucht diese Liste und gibt die erste Spalte jeder Gruppe ausgrep
filtert die erste Spalte (entsprechend jeder dritten Position in der ursprünglichen Liste)Verbessert meiner Meinung nach die Verständlichkeit. Die Leute wissen bereits, was diese Tools tun, daher ist es einfach, von links nach rechts zu lesen und zu überlegen, was passieren wird. Dieser Ansatz dokumentiert auch eindeutig die Schrittlänge (
-n3
) und das Filtermuster (9
), sodass die Variabilität einfach ist:Wenn wir Fragen zur "Effizienz" stellen, denken Sie unbedingt an die "Gesamtlebensdauereffizienz". Diese Berechnung beinhaltet die Bemühungen der Betreuer, den Code am Laufen zu halten, und wir Fleischsäcke sind die am wenigsten effizienten Maschinen im gesamten Betrieb.
quelle
Vielleicht das?
quelle
Verwenden Sie keine Shell-Befehle, wenn Sie effizient sein möchten. Beschränken Sie sich auf Pipes, Weiterleitungen, Ersetzungen usw. und Programme. Aus diesem Grunde
xargs
undparallel
Dienstprogramme vorhanden - weil bash While - Schleifen sind ineffizient und sehr langsam. Verwenden Sie Bash-Loops nur als letzte Lösung.Aber mit gut solltest du wohl etwas schneller werden
awk
.quelle
Meiner Meinung nach besteht die klarste (und wahrscheinlich auch performanteste) Lösung darin, die Variablen RS und ORS awk zu verwenden:
quelle
Verwenden des GNU-
sed
und POSIX- Shell-Skripts:Oder mit
bash
der Parametersubstitution :Nicht- GNU ( dh POSIX )
sed
undbash
:Oder portabler mit POSIX
sed
und Shell-Skript:Ausgabe von einem dieser:
quelle