Niech xargs używa aliasu zamiast binarnego

14

Bash 4.2 na CentOS 6.5:

W mojej ~/.bash_profilemam kilka aliasów, w tym:

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

dzięki czemu mogę automatycznie wyróżniać kolory i drukować numery linii automatycznie podczas działania grep. Jeśli uruchomię następujące czynności, wyróżnianie działa zgodnie z oczekiwaniami:

$ grep -Re 'regex_here' *.py

Jednak kiedy uruchomiłem to niedawno:

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

wyniki nie zostały podświetlone, a numery wierszy nie zostały wydrukowane, co zmusiło mnie do powrotu i wyraźnego dodania -n --color=alwaysdo greppolecenia.

  • Czy xargsnie odczytuje aliasów w środowisku?
  • Jeśli nie, czy istnieje sposób, aby to zrobić?
MattDMo
źródło
To pytania i odpowiedzi mają to, czego chcesz.
psimon
@psimon racja, w gruncie rzeczy mówi to, aby zrobić to, co już zrobiłem w swoim obejściu - musiałem ręcznie rozwinąć mój alias w xargspoleceniu. Próbuję się dowiedzieć, czy istnieje sposób, aby bezpośrednio wywołać mój alias xargs.
MattDMo
1
Czy próbowałeś export GREP_OPTIONS='-n --color=always'wcześniej przed komendą xargs?
doneal24,
@ DougO'Neal, dzięki! Dodam to do mojego .bash_profile. Napisz odpowiedź ...
MattDMo,

Odpowiedzi:

11

Alias ​​jest wewnętrzny dla powłoki, w której jest zdefiniowany. Nie jest widoczny dla innych procesów. To samo dotyczy funkcji powłoki. xargsjest osobną aplikacją, która nie jest powłoką, więc nie ma pojęcia aliasów ani funkcji.

Możesz zmusić xargs do wywoływania powłoki zamiast wywoływania grepbezpośrednio. Jednak samo wywołanie powłoki nie wystarczy, musisz również zdefiniować alias w tej powłoce. Jeśli alias jest zdefiniowany w twoim .bashrcpliku, możesz pobrać ten plik; może to jednak nie działać w przypadku .bashrcwykonywania innych zadań, które nie mają sensu w powłoce nieinteraktywnej.

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

Uważaj na zawiłości cytowania zagnieżdżonego podczas wpisywania wyrażenia regularnego. Możesz uprościć swoje życie, przekazując wyrażenie regularne jako parametr do powłoki.

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

Możesz wykonać wyszukiwanie aliasu jawnie. Wtedy xargszobaczysz grep -n --color=always.

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

W Zsh:

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

Nawiasem mówiąc, zauważ, że find … | xargs … łamie nazwy plików zawierające spacje (między innymi) . Możesz to naprawić, zmieniając rekordy rozdzielane wartościami zerowymi:

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

lub za pomocą -exec:

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

Zamiast dzwonić find, możesz zrobić wszystko całkowicie w powłoce. Wzorzec glob **/rekurencyjnie przechodzi przez katalogi. W bash musisz najpierw uruchomić shopt -s globstarten wzorzec glob.

grep regex_here **/*.py

Ma to kilka ograniczeń:

  • Jeśli wiele plików jest zgodnych (lub jeśli mają długie ścieżki), polecenie może się nie powieść, ponieważ przekracza maksymalną długość wiersza polecenia.
  • W bash ≤ 4.2 (ale nie w nowszych wersjach, ani w ksh czy zsh), **/powtarza się w symboliczne linki do katalogów.

Innym podejściem jest stosowanie substytucji procesu, jak sugeruje MariusMatutiae .

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

Jest to przydatne, gdy **/nie ma zastosowania: dla findwyrażeń złożonych lub w bash ≤4,2, gdy nie chcesz powtarzać się pod symbolicznymi linkami. Zauważ, że powoduje to uszkodzenie nazw plików zawierających spacje; obejściem tego problemu jest ustawienie IFSi wyłączenie globowania , ale zaczyna się ono nieco komplikować :

(IFS=$'\n'; set -f; grep regex_here <(find . -name '*.py') )
Gilles „SO- przestań być zły”
źródło
dziękuję za jasne wyjaśnienie, dlaczego aliasy nie są widoczne dla innych procesów
MattDMo
Można również użyć substytucji procesu, patrz moja odpowiedź.
MariusMatutiae
11

Posługiwać się 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
źródło
Dzięki za to, nie wiedziałem o podstępnej sztuczce kosmicznej.
MattDMo
Np. Jest także przydatny w sudo
1.61803
2

Proszę wziąć to za przykład innego podejścia, którego nie mogę znaleźć w powiązanym pytaniu SO :

Możesz napisać funkcję otoki, dla xargsktórej sprawdza, czy pierwszy argument jest aliasem, a jeśli tak, odpowiednio go rozwiń.

Oto kod, który robi dokładnie to, ale niestety wymaga powłoki Z, a zatem nie uruchamia 1: 1 z bash (i szczerze mówiąc, nie jestem przyzwyczajony do bashowania wystarczającego do przeniesienia go):

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]}"
}

Dowód, że to działa:

zsh% alias grep = "grep -n" ´                           # zawiera numer wiersza dopasowania
zsh% find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: 151: # data = test
foo / bar.p1: 122: # data = test # włączone numery linii
zsh% unalias grep 
zsh% find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: # data = test
foo / bar.p1: # data = test # nie podano numerów linii
zsh% 
mpy
źródło
1

Prostszym i bardziej eleganckim rozwiązaniem jest stosowanie zastępowania procesów :

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

Nie tworzy nowej powłoki, jak potok, co oznacza, że ​​nadal znajdujesz się w oryginalnej powłoce, w której zdefiniowany jest alias, a wynik jest dokładnie taki, jak chcesz.

Uważaj tylko, aby nie pozostawić spacji między przekierowaniem a nawiasami, w przeciwnym razie bash wygeneruje błąd. Według mojej najlepszej wiedzy podstawianie procesów jest obsługiwane przez Bash, Zsh, Ksh {88,93}, ale nie przez pdksh (powiedziano mi, że powinno to być jeszcze nie ).

MariusMatutiae
źródło
Myślę, że rozwój pdksh jest martwy. Mksh to mniej więcej następca projektu - „okazuje się dość trudny (koncepcja parsowania odbywa się w głowie tg @”) .
Gilles „SO- przestań być zły”
Podstawianie procesów jest dobrą metodą w przypadku złożonych findpoleceń, choć należy uważać, aby nie działały one na spacje i nie można tego naprawić tak łatwo, jak find | xargsto możliwe (przez przełączenie na -print0i -0lub użycie -exec). W stosownych przypadkach **/jest prostszy i bardziej niezawodny.
Gilles „SO- przestań być zły”
0

grep odczyta zestaw domyślnych opcji ze zmiennej środowiskowej GREP_OPTIONS. Jeśli umieścisz

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

w twoim .bashrc zmienna zostanie przekazana do podpowłok i uzyskasz oczekiwane wyniki.

doneal24
źródło
Ale nie należy umieszczać --line-numberalbo --color=alwaysw GREP_OPTIONSchyba, że to tylko dla jednego polecenia, to przełamać wiele skryptów. --color=autojest w porządku mieć to i to wszystko. Umieszczenie tego wiersza w tobie .bashrczłamie wiele rzeczy.
Gilles „SO- przestań być zły”
@Gilles Ustawienie złych aliasów lub nadpisanie domyślnych opcji dla dowolnego polecenia dla konta root jest złą rzeczą. Ustawienie tych opcji dla konta użytkownika prawdopodobnie nie spowoduje wielu problemów. Nie mogę wymyślić żadnych problematycznych skryptów użytkownika.
doneal24,
Zepsuje się prawie każdy skrypt, który używa grep w sposób wykraczający poza testowanie obecności wystąpienia. Na przykład, od /etc/init.d/cronw moim systemie: value=`egrep "^${var}=" "$ENV_FILE" | tail -n1 | cut -d= -f2` . Lub /usr/bin/pdfjam: pdftitl=`printf "%s" "$PDFinfo" | grep -e … | sed -e …` . Alias ​​nie stanowi problemu, ponieważ nie jest widoczny w skryptach.
Gilles 'SO - przestań być zły'
@Gilles Jestem świadomy wielu takich skryptów. Te, których nie mogę obejść, są generalnie uruchamiane tylko przez root (jak /etc/init.d/cron). Osobiście nie mam żadnych aliasy zdefiniowane na moich kontach użytkowników nie mogę ustawić opcje w pliku rc albo poprzez zmienne środowiskowe, aby zastąpić domyślne zachowanie poleceń. Wolę przewidywalność niż wygodę.
doneal24,
Aliasy nie zaburzają przewidywalności, ponieważ nie są widoczne w skryptach. Ustawienie GREP_OPTIONSprzerwy przewidywalność bardzo źle, z wyjątkiem kilku opcji, takich jak --color=auto(co jest, co zostało zaprojektowane dla).
Gilles 'SO - przestań być zły'