Welche allgemeinen Tipps haben Sie zum Golfen in Bash? Ich bin auf der Suche nach Ideen, die sich auf Code-Golf-Probleme im Allgemeinen anwenden lassen, die zumindest etwas spezifisch für Bash sind (z. B. "Kommentare entfernen" ist keine Antwort). Bitte posten Sie einen Tipp pro Antwort.
55
sh
und welche Shell erlaubt diesefor
Syntax? es ist ausdrücklich erlaubt inzsh
.sh
Versionen erlaubt war und dass Bash sie deswegen auch erlaubt, obwohl ich leider kein Zitat habe.csh
wahrscheinlich - so haben sie in dieser Shell gearbeitet.ksh93
;{1..10}
bash
printf %s\\n {1..10}
for((;i++<10)){ echo $i;}
ist kürzer alsfor i in {1..10};{ echo $i;}
Für die arithmetische Erweiterung verwenden Sie
$[…]
anstelle von$((…))
:Verwenden Sie in arithmetischen Erweiterungen nicht
$
:Die arithmetische Erweiterung wird für indizierte Array-Indizes ausgeführt. Verwenden Sie daher
$
keine der folgenden Optionen:Verwenden Sie in arithmetischen Erweiterungen nicht
${…}
:quelle
while((i--))
, was funktioniert, mitwhile[i--]
oderwhile $[i--]
hat bei mir nicht geklappt. GNU Bash, Version 4.3.46 (1)y=`bc<<<"($x*2.2)%10/1"`
... Beispiel für die Verwendungbc
für nicht ganzzahlige Berechnungen ... Beachten Sie, dass/1
am Ende die resultierende Dezimalstelle auf ein int abgeschnitten wird.s=$((i%2>0?s+x:s+y))
... Beispiel für die Verwendung eines ternären Operators in der Bash-Arithmetik. Es ist kürzer alsif..then..else
oder[ ] && ||
GNU bash, version 5.0.2(1)-release (x86_64-apple-darwin16.7.0)
und es ist nicht in meinem.Die normale, langwierige und langweilige Art, eine Funktion zu definieren, ist
Wie dieser Typ herausgefunden hat, braucht man unbedingt das Leerzeichen davor
CODE
und das Semikolon danach.Dies ist ein kleiner Trick, den ich von @DigitalTrauma gelernt habe :
Das ist zwei Zeichen kürzer und funktioniert auch, vorausgesetzt, Sie müssen nach der Rückkehr der Funktion keine Änderungen an den Variablenwerten vornehmen ( die Klammern führen den Text in einer Subshell aus ).
Wie @ jimmy23013 in den Kommentaren betont , sind möglicherweise sogar die Klammern unnötig.
Das Bash-Referenzhandbuch zeigt, dass Funktionen wie folgt definiert werden können:
Ein zusammengesetzter Befehl kann sein:
until
,while
oderfor
if
,case
,((...))
oder[[...]]
(...)
oder{...}
Das heißt, alle folgenden Punkte sind gültig:
Und ich habe geschweifte Klammern wie ein Trottel benutzt ...
quelle
f()while ...
f()if ...
andere zusammengesetzte Befehle verwenden können.f()CODE
sei legal. Es stellt sich heraus, dass diesf()echo hi
in pdksh und zsh legal ist, aber nicht in bash.for
da standardmäßig Positionals verwendet werden:f()for x do : $x; done;set -x *;PS4=$* f "$@"
oder so.:
Ist ein Befehl, der nichts tut, ist sein Exit-Status immer erfolgreich, sodass er anstelle von verwendet werden kanntrue
.quelle
:(){:|:}
Mehr Tipps
Missbrauch des ternären Operators
((test)) && cmd1 || cmd2
oder[ test ] && cmd1 || cmd2
so viel wie möglich.Beispiele (Längenangaben schließen immer die oberste Zeile aus):
Wenn nur ternäre Operatoren verwendet werden, kann dies leicht verkürzt werden auf:
Wie nyuszika7h in den Kommentaren ausführte, könnte dieses spezielle Beispiel noch weiter verkürzt werden
case
:Ziehen Sie außerdem Klammern möglichst Klammern vor. Da Klammern ein Metazeichen und kein Wort sind, benötigen sie in keinem Kontext Leerzeichen. Dies bedeutet auch, dass so viele Befehle wie möglich in einer Subshell ausgeführt werden, da geschweifte Klammern (dh
{
und}
) reservierte Wörter und keine Metazeichen sind und daher auf beiden Seiten Leerzeichen enthalten müssen, um eine korrekte Analyse durchzuführen, Metazeichen jedoch nicht. Ich gehe davon aus, dass Sie inzwischen wissen, dass sich Subshells nicht auf die übergeordnete Umgebung auswirken. Wenn Sie also davon ausgehen, dass alle Beispielbefehle sicher in einer Subshell ausgeführt werden können (was ohnehin nicht typisch ist), können Sie den obigen Code hierauf verkürzen :Wenn Sie dies nicht können, kann die Verwendung von Klammern das Problem möglicherweise verringern. Beachten Sie, dass es nur für Ganzzahlen funktioniert, was es für die Zwecke dieses Beispiels unbrauchbar macht (aber es ist viel besser als die Verwendung
-eq
für Ganzzahlen).Eine weitere Sache, vermeiden Sie Anführungszeichen, wo es möglich ist. Mit dem obigen Hinweis können Sie ihn weiter minimieren. Beispiel:
Ziehen Sie unter Testbedingungen mit wenigen Ausnahmen einfache Klammern doppelten Klammern so weit wie möglich vor. Es werden kostenlos zwei Zeichen abgelegt, aber in einigen Fällen ist es nicht so robust (es ist eine Bash-Erweiterung - siehe unten für ein Beispiel). Verwenden Sie auch das Argument single equals und nicht double. Es ist ein Freizeichen zum Ablegen.
Beachten Sie diese Einschränkung, insbesondere beim Überprüfen auf Null-Ausgabe oder eine undefinierte Variable:
In der Tat wäre dieses spezielle Beispiel am besten mit
case
...in
:Die Moral dieses Beitrags lautet also:
if
/if-else
/ etc. Konstrukte.case
...in
, da es einige Bytes einsparen kann, insbesondere beim String-Matching.PS: Hier ist eine Liste von Metazeichen, die in Bash unabhängig vom Kontext erkannt werden (und Wörter trennen können):
BEARBEITEN: Wie manatwork hervorhob, funktioniert der doppelte Klammertest nur für ganze Zahlen. Außerdem habe ich indirekt festgestellt, dass der
==
Operator von Leerzeichen umgeben sein muss . Korrigierte meinen Beitrag oben.Ich war auch zu faul, um die Länge der einzelnen Segmente neu zu berechnen, also habe ich sie einfach entfernt. Es sollte einfach genug sein, bei Bedarf einen Stringlängenrechner online zu finden.
quelle
[ $t=="hi" ]
wird immer mit 0 ausgewertet, da es analysiert wird als[ -n "STRING" ]
.(($t=="hi"))
wird immer auf 0 ausgewertet, solange $ t keinen numerischen Wert hat, da Zeichenfolgen in arithmetischen Auswertungen zu Ganzzahlen gezwungen werden. Einige Testfälle: pastebin.com/WefDzWbLcase
wäre hier kürzer. Außerdem brauchen Sie vorher kein Leerzeichen}
, aber nachher{
.=
weniger robust als==
?=
wird von POSIX beauftragt,==
nicht wahr?Statt
grep -E
,grep -F
,grep -r
, Einsatzegrep
,fgrep
,rgrep
, spart zwei Zeichen. Die kürzeren sind veraltet, funktionieren aber einwandfrei.(Sie haben pro Antwort um einen Tipp gebeten!)
quelle
Pgrep
fürgrep -P
. Obwohl ich sehe, wie es leicht zu verwechseln istpgrep
, die verwendet wird, um Prozesse nachzuschlagen.grep -o
vielAuf Element 0 eines Arrays kann nur mit dem Variablennamen zugegriffen werden, wobei beim Speichern von fünf Bytes explizit ein Index von 0 angegeben wird:
quelle
Wenn Sie den Inhalt einer Variablen an STDIN des nächsten Prozesses in einer Pipeline übergeben müssen, wird die Variable häufig in eine Pipeline zurückgegeben. Aber Sie können dasselbe mit einem
<<<
Bash-Here-String erreichen :quelle
s=code\ golf
,echo $s|
und<<<$s
(dass die beiden letzteren Arbeit im Auge behalten nur , weil es keine wiederholte Plätze sind, etc.).Vermeiden Sie
$( ...command... )
, dass es eine Alternative gibt, die ein Zeichen spart und dasselbe tut:quelle
$( )
ist es erforderlich, wenn Sie verschachtelte Befehlsersetzungen haben. sonst müsstest du dem inneren``
$()
die Ersetzung beispielsweise auf meinem Computer anstelle desscp
Zielcomputers auszuführen . In den meisten Fällen sind sie identisch.$()
können sie tun, wenn Sie die Dinge richtig zitieren. (es sei denn, Sie benötigen Ihr Kommando, um etwas zu überleben, das Mungos,$
aber keine Backticks enthält). Es gibt einige subtile Unterschiede darin, Dinge in ihnen zu zitieren. mywiki.wooledge.org/BashFAQ/082 erklärt einige Unterschiede. Verwenden Sie niemals Backticks, es sei denn, Sie spielen Golf.echo `bc <<<"\`date +%s\`-12"`
... (Es ist schwierig, ein Beispiel mit einem Backtick im Kommentar zu veröffentlichen!)Dient
if
zum Gruppieren von BefehlenIm Vergleich zu diesem Tipp, mit dem das
if
überhaupt entfernt wird, sollte dies nur in sehr seltenen Fällen besser funktionieren, zif
.Wenn Sie eine Befehlsgruppe haben, die mit a endet
if
, wie folgt:Sie können die Befehle
if
stattdessen vorher in die Bedingung einschließen:Oder wenn Ihre Funktion mit einem endet
if
:Sie können die Klammern entfernen:
quelle
[test] && $if_true || $else
in diesen Funktionen verwenden und einige Bytes sparen.&&
und||
Verwenden Sie Arithmetik
(( ... ))
für BedingungenSie könnten ersetzen:
durch
(Hinweis: Nachher ist kein Leerzeichen mehr vorhanden.
if
)oder auch
... oder wenn nur ein Befehl
Weiter: Variableneinstellung im Rechenkonstrukt ausblenden:
oder gleich
... wo, wenn i> 5 ist, dann ist c = 1 (nicht 0;)
quelle
[ ]
anstelle von verwenden(())
.[ ]
brauchst du das Dollarzeichen für Variable. Ich verstehe nicht, wie Sie dasselbe mit derselben oder einer geringeren Länge erreichen können, wenn Sie verwenden[ ]
.((...))
, ist weder eine neue Zeile noch ein Leerzeichen erforderlich. ZBfor((;10>n++;)){((n%3))&&echo $n;}
online ausprobieren!Eine kürzere Syntax für Endlosschleifen (die mit
break
oderexit
Anweisungen maskiert werden können) istDies ist kürzer als
while true;
undwhile :;
.Wenn Sie nicht brauchen
break
(exit
als einzige Möglichkeit, um zu entkommen), können Sie stattdessen eine rekursive Funktion verwenden.Wenn Sie break benötigen, aber kein exit benötigen und keine Variablenänderung außerhalb der Schleife vornehmen müssen, können Sie eine rekursive Funktion mit runden Klammern um den Körper verwenden , die den Funktionskörper in einer Subshell ausführt.
quelle
Einzeilige
for
SchleifenEin arithmetischer Ausdruck, der mit einer Bereichserweiterung verknüpft ist, wird für jedes Element im Bereich ausgewertet. Zum Beispiel das Folgende:
wird
expression
10 mal ausgewertet .Dies ist häufig erheblich kürzer als die entsprechende
for
Schleife:Wenn es Ihnen nichts ausmacht, Fehler zu befehlen, die nicht gefunden wurden, können Sie das Initial entfernen
:
. Bei Iterationen über 10 können Sie auch Zeichenbereiche verwenden, die beispielsweise{A..z}
58-mal wiederholt werden.Als praktisches Beispiel ergeben beide die ersten 50 Dreieckszahlen in einer eigenen Zeile:
quelle
for((;0<i--;)){ f;}
Argumente durchlaufen
Wie in bemerkte , ohne „in foo bar ...“ Schlag „für“ Schleifenteil , das
in "$@;"
infor x in "$@;"
redundant ist.Von
help for
:Wenn wir beispielsweise alle Zahlen, denen Positionsargumente gegeben wurden, mit einem Bash-Skript oder einer Funktion quadrieren möchten, können wir dies tun.
Probieren Sie es online!
quelle
Alternative zur Katze
Angenommen, Sie versuchen, eine Datei zu lesen und in etwas anderem zu verwenden. Was Sie tun könnten, ist:
Wenn der Inhalt von
bar
warfoobar
, würde dies druckenfoo foobar
.Es gibt jedoch eine Alternative, wenn Sie diese Methode verwenden, die 3 Bytes einspart:
quelle
<bar
allein nicht funktioniert, aber das Platzieren in Backticks?<
versetzt eine Datei in einen Befehl, aber in diesem Fall wird sie aufgrund einer Eigenart in die Standardausgabe gestellt. Die Backticks bewerten dies gemeinsam.`cat`
?Verwenden Sie
[
anstelle von[[
undtest
wenn möglichBeispiel:
Verwenden Sie
=
statt==
zum VergleichBeispiel:
Beachten Sie, dass Sie Leerzeichen um das Gleichheitszeichen haben müssen, sonst funktioniert es nicht. Gleiches gilt
==
aufgrund meiner Tests.quelle
[
vs.[[
kann über die Höhe der erforderlichen Zitate abhängen: pastebin.com/UPAGWbDQ[
...]
==/bin/test
, aber[[
...]]
! =/bin/test
Und man sollte niemals[
...]
vor[[
...]]
außerhalb von CodegolfAlternativen zu
head
line
ist drei Bytes kürzer alshead -1
, aber wird als veraltet .sed q
ist zwei Bytes kürzer alshead -1
.sed 9q
ist ein Byte kürzer alshead -9
.quelle
line
von util-linux - Paket eine einzige Zeile zu lesen.tr -cd
ist kürzer alsgrep -o
Wenn Sie zum Beispiel Leerzeichen zählen müssen, gibt
grep -o <char>
(nur das Übereinstimmende drucken) 10 Bytes aus, währendtr -cd <char>
(Komplement löschen von<char>
) 9 ergibt.( Quelle )
Beachten Sie, dass beide leicht unterschiedliche Ausgaben liefern.
grep -o
Gibt zeilengetrennte Ergebnisse zurück, währendtr -cd
sich alle in derselben Zeile befinden. Dies isttr
möglicherweise nicht immer günstig.quelle
Dateinamen kürzen
Bei einer kürzlich durchgeführten Herausforderung habe ich versucht, die Datei zu lesen.
/sys/class/power_supply/BAT1/capacity
Dies kann jedoch auf verkürzt werden,/*/*/*/*/capac*y
da keine andere Datei mit diesem Format vorhanden ist.Wenn Sie beispielsweise ein Verzeichnis
foo/
mit den Dateien hattenfoo, bar, foobar, barfoo
und auf die Datei verweisen möchtenfoo/barfoo
, können Siefoo/barf*
ein Byte speichern.Das steht
*
für "irgendetwas" und entspricht dem regulären Ausdruck.*
.quelle
Verwenden Sie
:
anstelle von eine Pipe zum Befehl/dev/null
. Das:
eingebaute System frisst alle Eingaben.quelle
echo a|tee /dev/stderr|:
druckt nichts.echo a|tee /dev/stderr|:
Ich habe ein auf meinem Computer gedruckt, aber anderswo könnte Sigpipe zuerst Tee töten. Es kann von der Version von abhängentee
.tee >(:) < <(seq 1 10)
Wird funktionieren, abertee /dev/stderr | :
nicht. Aucha() { :;};tee /dev/stderr < <(seq 1 10)| a
nichts drucken.:
Intrinsic überhaupt nicht ... wenn du eine Eingabe in den Doppelpunkt unterstellst, könntest du eine Pipe zu einem In-Out-Fehler fluten ... aber du kannst eine Umleitung schweben lassen durch einen Doppelpunkt, oder lassen Sie einen Prozess damit fallen ... oder wie auch immer dieser Pseudovorschlag zutrifft ... auch, sind Sie sich bewusst, dass Sie fast so gespenstisch sind wie ich? ... vermeiden Sie auf jeden:| while i>&$(($??!$?:${#?})) command shit; do [ -s testitsoutput ]; done
< <(psycho shit i can alias to crazy math eat your world; okay? anyway, ksh93 has a separate but equal composite char placement)
split
hat eine andere (veraltete, aber niemanden interessierende) Syntax zum Aufteilen von Eingaben inN
einzelne Zeilenabschnitte: Anstelle von könnensplit -lN
Siesplit -N
zsplit -9
.quelle
Erweitern Sie die Tests
Im Wesentlichen ist die Shell eine Art Makrosprache oder zumindest eine hybride oder irgendeine Art von Sprache. Jede Befehlszeile kann grundsätzlich in zwei Teile unterteilt werden: den Parsing- / Eingabeteil und den Expansions- / Ausgabeteil.
Der erste Teil ist das, worauf sich die meisten Leute konzentrieren, weil es am einfachsten ist: Sie sehen, was Sie bekommen. Der zweite Teil ist das, was viele vermeiden, überhaupt zu versuchen, sehr gut zu verstehen, und ist der Grund, warum Leute sagen, dass Dinge
eval
böse sind und immer Ihre Erweiterungen zitieren - die Leute wollen, dass das Ergebnis des ersten Teils gleich dem ersten ist. Das ist in Ordnung - aber es führt zu unnötig langen Code-Verzweigungen und einer Menge unnötiger Tests.Erweiterungen sind Selbsttests . Die
${param[[:]#%+-=?]word}
Formulare sind mehr als genug, um den Inhalt eines Parameters zu validieren, sind verschachtelbar und basieren auf der Auswertung für NUL - was die meisten Leute sowieso von Tests erwarten.+
kann in Schleifen besonders praktisch sein:... während
read
nicht leere Zeilen"${r:+set}"
eingezogen werden, werden"set"
die Positionsangaben$r
angehängt. Aber wenn eine leere Zeile istread
,$r
ist sie leer und"${r:+set}"
erweitert sich zu""
- was ein ungültiger Befehl ist. Da die Befehlszeile jedoch erweitert wird, bevor der""
Nullbefehl durchsucht wird, werden auch"${r:=$*}"
die Werte aller im ersten Byte verketteten Positionsangaben übernommen$IFS
.r()
könnte im|{
zusammengesetzten Befehl;}
mit einem anderen Wert erneut aufgerufen werden$IFS
, um auch den nächsten Eingabeabsatz abzurufen , daread
das\n
Puffern einer Shell über die nächste ewline in der Eingabe hinaus unzulässig ist.quelle
Verwenden Sie die Schwanzrekursion, um Schleifen zu verkürzen:
Diese sind im Verhalten gleichwertig (obwohl wahrscheinlich nicht im Speicher / PID-Gebrauch):
Und das sind ungefähr gleichbedeutend:
(Technisch gesehen führen die letzten drei den Körper immer mindestens einmal aus)
Für die Verwendung
$0
muss sich Ihr Skript in einer Datei befinden, die nicht in die Bash-Eingabeaufforderung eingefügt wird.Möglicherweise läuft Ihr Stapel über, aber Sie sparen einige Bytes.
quelle
Manchmal ist es kürzer, den
expr
eingebauten Ausdruck anstelle des üblichen Ausdrucks für die Anzeige des Ergebnisses eines einfachen arithmetischen Ausdrucks zu verwendenecho $[ ]
. Zum Beispiel:ist ein Byte kürzer als:
quelle
Verwenden Sie
pwd
anstelle vonecho
, um eine Ausgabezeile zu generierenMöchten Sie eine Zeile in stdout einfügen, sich aber nicht um den Inhalt kümmern und Ihre Antwort auf Shell-Builtins beschränken?
pwd
ist ein Byte kürzer alsecho
.quelle
Beim Drucken von Zeichenfolgen können Anführungszeichen weggelassen werden.
Ausgabe in SM-T335 LTE, Android 5.1.1:
quelle
Wenn Sie nicht zusammenhängende Array-Elemente zuweisen, können Sie die aufeinanderfolgenden Indizes von zusammenhängenden Blöcken dennoch überspringen:
Das Ergebnis ist dasselbe:
Nach
man bash
:quelle
Gibt das erste Wort in einer Zeichenfolge aus
Wenn sich die Zeichenfolge in der Variablen befindet
a
und keine Escape- und Formatierungszeichen (\
und%
) enthält, verwenden Sie Folgendes:Es wäre jedoch länger als der folgende Code, wenn das Ergebnis in einer Variablen gespeichert und nicht gedruckt werden müsste:
quelle
2 embed loop mit 1
for
Anweisung machen:quelle
In Anführungszeichen gesetzte Zeichenfolgen zuweisen und drucken
Wenn Sie einer Variablen eine Zeichenfolge in Anführungszeichen zuweisen und dann den Wert dieser Variablen ausgeben möchten, lautet die übliche Vorgehensweise wie folgt:
Wenn
a
zuvor nicht festgelegt, kann dies verkürzt werden auf:Wenn
a
zuvor festgelegt, sollte dies stattdessen verwendet werden:Beachten Sie, dass dies nur sinnvoll ist, wenn die Zeichenfolge Anführungszeichen erfordert (z. B. Leerzeichen). Ohne Anführungszeichen
a=123;echo $a
ist das genauso kurz.quelle
${a+foo}
setzt nichta
.