Lassen Sie Xargs Alias ​​anstelle von Binär verwenden

13

Bash 4.2 unter CentOS 6.5:

In meinem habe ~/.bash_profileich eine Reihe von Aliasen, darunter:

alias grep='grep -n --color=always'

damit ich beim laufen automatisch farbmarkierungen und zeilennummern drucken kann grep. Wenn ich Folgendes ausführe, funktioniert die Hervorhebung wie erwartet:

$ grep -Re 'regex_here' *.py

Als ich dies jedoch kürzlich ausführte:

$ find . -name '*.py' | xargs grep -E 'regex_here'

Die Ergebnisse wurden nicht hervorgehoben und die Zeilennummern wurden nicht gedruckt, sodass ich zurückgehen und -n --color=alwaysden grepBefehl explizit ergänzen musste .

  • Liest xargsnicht Aliase in der Umgebung?
  • Wenn nicht, gibt es eine Möglichkeit, dies zu erreichen?
MattDMo
quelle
Diese Frage & Antwort hat was Sie wollen.
Psimon
@psimon richtig, das bedeutet im Wesentlichen, dass ich das tun soll, was ich bereits in meinem Workaround getan habe - ich musste meinen Alias ​​im xargsBefehl manuell erweitern . Ich versuche herauszufinden, ob es eine Möglichkeit gibt, von der aus ich meinen Alias ​​direkt anrufen kann xargs.
MattDMo
1
Haben Sie es export GREP_OPTIONS='-n --color=always'vor Ihrem xargs-Befehl versucht ?
doneal24
@ DougO'Neal danke, das hat funktioniert! Ich werde das zu meinem hinzufügen .bash_profile. Fühlen Sie sich frei, eine Antwort zu schreiben ...
MattDMo

Antworten:

10

Ein Alias ​​befindet sich innerhalb der Shell, in der er definiert ist. Es ist für andere Prozesse nicht sichtbar. Gleiches gilt für Shell-Funktionen. xargsist eine separate Anwendung, die keine Shell ist und daher kein Konzept für Aliase oder Funktionen hat.

Sie können dafür sorgen, dass XARGs eine Shell aufrufen, anstatt sie grepdirekt aufzurufen . Es reicht jedoch nicht aus, nur eine Shell aufzurufen, sondern Sie müssen auch den Alias ​​in dieser Shell definieren. Wenn der Alias ​​in Ihrem Namen definiert ist .bashrc, können Sie diese Datei als Quelle verwenden. Dies funktioniert jedoch möglicherweise nicht, wenn Sie .bashrcandere Aufgaben ausführen, die in einer nicht interaktiven Shell keinen Sinn ergeben.

find . -name '*.py' | xargs bash -c '. ~/.bashrc; grep -E regex_here "$@"' _

Achten Sie beim Eingeben des regulären Ausdrucks auf die Feinheiten des verschachtelten Zitierens. Sie können Ihr Leben vereinfachen, indem Sie den regulären Ausdruck als Parameter an die Shell übergeben.

find . -name '*.py' | xargs bash -c '. ~/.bashrc; grep -E "$0" "$@"' regex_here

Sie können die Alias-Suche explizit durchführen. Dann xargswerde ich sehen grep -n --color=always.

find . -name '*.py' | xargs "${BASH_ALIASES[grep]}" regex_here

In zsh:

find . -name '*.py' | xargs $aliases[grep] regex_here

Beachten Sie übrigens, dass find … | xargs … bei Dateinamen, die (unter anderem) Leerzeichen enthalten , Brüche auftreten . Sie können dieses Problem beheben, indem Sie in durch Nullen getrennte Datensätze wechseln:

find . -name '*.py' -print0 | xargs -0 "${BASH_ALIASES[grep]}" regex_here

oder mit -exec:

find . -name '*.py' -exec "${BASH_ALIASES[grep]}" regex_here {} +

Anstatt anzurufen find, können Sie alles komplett in der Shell erledigen. Das Glob-Muster **/durchläuft Verzeichnisse rekursiv. In Bash müssen Sie zuerst ausführen shopt -s globstar, um dieses Glob-Muster zu aktivieren.

grep regex_here **/*.py

Dies hat einige Einschränkungen:

  • Wenn viele Dateien übereinstimmen (oder wenn sie lange Pfade haben), schlägt der Befehl möglicherweise fehl, weil er die maximale Befehlszeilenlänge überschreitet.
  • In bash ≤4.2 (aber nicht in neueren Versionen, weder in ksh noch in zsh) **/werden symbolische Verknüpfungen zu Verzeichnissen erstellt.

Ein anderer Ansatz ist die Verwendung der Prozesssubstitution, wie von Marius Matutiae vorgeschlagen .

grep regex_here <(find . -name '*.py')

Dies ist nützlich, wenn dies **/nicht zutrifft: für komplexe findAusdrücke oder in Bash ≤4.2, wenn Sie nicht unter symbolischen Links rekursiv arbeiten möchten. Beachten Sie, dass dies bei Dateinamen mit Leerzeichen abbricht. Ein Workaround ist das Setzen IFSund Deaktivieren von Globbing , aber es wird langsam etwas komplexer:

(IFS=$'\n'; set -f; grep regex_here <(find . -name '*.py') )
Gilles 'SO - hör auf böse zu sein'
quelle
Vielen Dank für die klare Erklärung, warum Aliase für andere Prozesse nicht sichtbar sind
MattDMo
Man kann auch die Prozessersetzung verwenden, siehe meine Antwort.
MariusMatutiae
11

Verwenden alias xargs='xargs '

alias: alias [-p] [name[=value] ... ]
(snip)
A trailing space in VALUE causes the next word to be checked for
alias substitution when the alias is expanded.
1.61803
quelle
Vielen Dank, ich wusste nichts über den Trick mit dem nachgestellten Leerzeichen.
MattDMo
Np. Es ist auch nützlich mit sudo
1.61803
2

Bitte nehmen Sie dies als Demonstration eines anderen Ansatzes, den ich in der zugehörigen SO-Frage nicht finden kann :

Sie können eine Wrapper-Funktion schreiben, bei xargsder überprüft wird, ob das erste Argument ein Alias ​​ist, und wenn ja, erweitern Sie es entsprechend.

Hier ist ein Code, der genau das tut, aber leider die Z-Shell benötigt und daher nicht 1: 1 mit bash läuft (und ehrlich gesagt bin ich es nicht gewohnt, genug zu bash , um es zu portieren):

xargs () {
        local expandalias
        if [[ $(which $1) =~ "alias" ]]; then
                expandalias=$(builtin alias $1) 
                expandalias="${${(s.'.)expandalias}[2]}"
        else
                expandalias=$1
        fi
        command xargs ${(z)expandalias} "${(z)@[2,-1]}"
}

Beweis, dass es funktioniert:

zsh% alias grep = "grep -n"                           # enthält die Zeilennummer der Übereinstimmung
zsh% find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: 151: # data = test
foo / bar.p1: 122: # data = test # line numbers included
zsh% unalias grep 
zsh% find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: # data = test
foo / bar.p1: # data = Test # Zeilennummern nicht enthalten
zsh% 
mpy
quelle
1

Eine einfachere und elegantere Lösung ist die Verwendung der Prozesssubstitution :

grep -E 'regex_here' <( find . -name '*.py')

Es wird keine neue Shell erstellt, wie dies bei der Pipe der Fall ist. Dies bedeutet, dass Sie sich noch in Ihrer ursprünglichen Shell befinden, in der der Alias ​​definiert ist, und dass die Ausgabe genau Ihren Wünschen entspricht.

Achten Sie nur darauf, dass zwischen Umleitung und Klammer kein Leerzeichen bleibt, da sonst Bash einen Fehler auslöst. Nach meinem besten Wissen wird die Prozessersetzung von Bash, Zsh, Ksh {88,93}, aber nicht von pdksh unterstützt (mir wurde gesagt, dass dies noch nicht geschehen sollte ).

MariusMatutiae
quelle
Ich denke, pdksh Entwicklung ist tot. Mksh ist mehr oder weniger ein Nachfolgeprojekt - "ziemlich schwer, es stellt sich heraus (das Parsing-Konzept wird im Kopf von tg @ durchgeführt)" .
Gilles 'SO- hör auf böse zu sein'
Die Prozessersetzung ist eine gute Methode für komplexe findBefehle, obwohl Sie darauf achten müssen, dass sie in Leerzeichen unterbrochen wird und nicht so einfach behoben werden find | xargskann (durch Umschalten auf -print0und -0oder Verwenden von -exec). Gegebenenfalls **/ist einfacher und robuster.
Gilles 'SO- hör auf böse zu sein'
0

grep liest eine Reihe von Standardoptionen aus der Umgebungsvariablen GREP_OPTIONS. Wenn Sie setzen werden

 export GREP_OPTIONS='--line-number --color=always'

In Ihrer .bashrc-Datei wird die Variable dann an Subshells weitergeleitet und Sie erhalten die erwarteten Ergebnisse.

doneal24
quelle
Aber nicht setzen --line-numberoder --color=alwaysin GREP_OPTIONSes sei denn , es ist nur für einen Befehl, dies wird eine Menge von Skripten zu brechen. --color=autoEs ist in Ordnung, dort zu sein, und das ist alles. Wenn Sie diese Zeile in Ihren .bashrcWillen setzen, werden Sie viele Dinge kaputt machen.
Gilles 'SO- hör auf böse zu sein'
@Gilles Das Festlegen von Aliasen oder das Überschreiben der Standardoptionen für einen Befehl für das Root-Konto ist eine schlechte Sache. Es ist unwahrscheinlich, dass das Festlegen dieser Optionen für ein Benutzerkonto viele Probleme verursacht. Ich kann keine Benutzerskripte finden, die problematisch sind.
doneal24
Nahezu jedes Skript, das grep auf eine Art und Weise verwendet, die über das Testen des Vorhandenseins eines Ereignisses hinausgeht, bricht ab. Zum Beispiel aus /etc/init.d/cronauf meinem System: value=`egrep "^${var}=" "$ENV_FILE" | tail -n1 | cut -d= -f2` . Oder von /usr/bin/pdfjam: pdftitl=`printf "%s" "$PDFinfo" | grep -e … | sed -e …` . Ein Alias ​​ist kein Problem, da er in Skripten nicht enthalten ist.
Gilles 'SO- hör auf böse zu sein'
@ Gilles Mir sind viele solche Skripte bekannt. Diejenigen, die ich nicht umgehen kann, werden im Allgemeinen nur von root ausgeführt (z. B. /etc/init.d/cron). Persönlich habe ich nicht alle Aliase definiert auf meine Benutzerkonten noch richte ich Optionen in einer RC - Datei oder über Umgebungsvariablen das Standardverhalten von Befehlen außer Kraft zu setzen. Ich bevorzuge Vorhersehbarkeit gegenüber Bequemlichkeit.
doneal24
Aliase brechen die Vorhersagbarkeit nicht, da sie von Skripten nicht erkannt werden. Das Einstellen der GREP_OPTIONSVorhersagbarkeit von Pausen ist sehr schlecht, mit Ausnahme einiger Optionen --color=auto(für die es entwickelt wurde).
Gilles 'SO- hör auf böse zu sein'