Upozornenie
Článok má novšiu verziu.
Úvod
Každý druhý článok o xargs
sa začína v duchu „… jedným z najpodceňovanejších príkazov Unixu je…”. Tento nebude iný.
Mnohokrát sa stáva, že výstupom programu je niekoľko slov oddelených bielym miestom (napr. slová na samostatných riadkoch), ktoré chceme postupne jeden za druhým spracovávať a posielať ako parameter do iného programu. Niečo v duchu:
pre každé slovo R
spracuj R
Priamo for
cyklus! V shellscriptingu je však for
prekérny: vyžaduje totiž podivnú viacriadkovú syntax… a komu sa chce kvôli jednoduchým jednorazovým veciam zakladať skripty, písať shebangy, chmod
ovať a patlať sa s editorom.
Túto rolu vie mnohokrát zastúpiť xargs
.
O xargs
Stručne povedané:
xargs
zostavuje zo štandardného vstupu príkazový riadok a vykonáva ho.
Povedané inými slovami: ak máte na štandardnom vstupe slová oddelené bielym miestom, a v inom príkaze ich potrebujete nasekať do parametrov, xargs
vás zachráni. A naozaj nemusíte for
ovať.
Ukážková úloha: vypíšte počty riadkov všetkých shellskriptov v aktuálnom adresári
Všetci vieme, že sa to dá spraviť jednoduchým (ehm) find
om:
find . *.sh -maxdepth 1 -exec wc -l {} \;
Syntax find
u je záludná: treba si pamätať chlpaté zátvorky a najmä nezabudnúť ukončiť -exec
escapovanou bodkočiarkou. Nehovoriac o nutnosti obmedziť hľadanie na jedno vnorenie cez -maxdepth
.
S xargs
by to šlo jednoduchšie:
echo *.sh | xargs wc -l
Využijeme tu klasickú shellovskú pathname expansion (expanzia hviezdičky), kde echo
vráti zoznam všetkých súborov (a adresárov) končiacich sa na *.sh
.
Výsledok napr:
mena.sh premenuj.sh velkosti.sh vyrobsubory.sh
Vieme, že wc
vie zobrať ako parameter názov súboru, pre ktorý spočíta riadky/znaky/bajty:
novotnyr@s:~$ wc -l mena.sh
14 mena.sh
Príkaz wc
samozrejme podporuje aj viacero súborov:
novotnyr@s:~$ wc -l mena.sh premenuj.sh
14 mena.sh
8 premenuj.sh
22 total
(Všimnite si bonusový riadok s total
uvádzajúci sumarizované dáta.)
Ak by sme vedeli nasekať všetky súbory ako parametre pre wc
, získali by sme všetko, čo potrebujeme. A presne toto urobí xargs
.
V štandardnom správaní vyzbiera slová zo štandardného vstupu (slovom sa teraz rozumie reťazec oddelený medzerami) a rovno ich vykydne do parametrov pre príkaz uvedený za ním. V našom príklade sa totiž v skutočnosti vykoná toto:
wc -l mena.sh premenuj.sh velkosti.sh vyrobsubory.sh
Uvedené použitie je nielen jednoduchšie na zápis, ale dokonca pobeží rýchlejšie než find/exec
. Vykoná sa totiž len jedno spustenie wc -l
, na rozdiel od prvého prípadu, kde sa wc
spúšťa pre každý nájdený súbor zvlášť.
Limity operačného systému
Operačný systém obvykle kladie limity na maximálnu dĺžku riadku, ktorý sa dá vykonať. V súčasných systémoch to už nie je až také kritické (napr. náš debianovský server má limit 2094774 znakov, teda cca 2 MB dát.) Ak by aj napriek tomu k niečomu takému došlo, xargs
sa s tým vie vysporiadať — to totiž bol jeden z jeho hlavných účelov.
Pomocou parametra -n
vieme povedať, koľko slov zo štandardného vstupu sa má použiť v jednej várke spustenia.
echo *.sh | xargs -n2 wc -l
Pre -n2
to znamená, že xargs
bude brať dvojice slov a použije ich ako parametre pre wc
. Keďže máme dve dvojice, wc
sa spustí dvakrát. Aha, výsledok:
14 mena.sh
8 premenuj.sh
22 total
18 velkosti.sh
8 vyrobsubory.sh
26 total
Dôsledkom je dvojnásobný výpis total
ov, čo môžeme vyriešiť nahrubo: jednoducho spustíme wc
pre každý súbor zvlášť:
echo *.sh | xargs -n2 wc -l
Medzery a xargs
Pre xargs
platí základná zásada: berú sa slová oddelené bielym miestom (medzery, tabulátory…). To sa prejaví hlavne v prípade, ak máme súbory s medzerami v názve — v takom prípade začne xargs
a wc
robiť psie kusy. Napríklad súbor subor 1.txt
v kombinácii s xargs
spôsobí volanie:
wc -l subor 1.txt
Čo znamená, že sa úbohý wc
pokúsi zrátať riadky v súbore subor
a potom v súbore 1.txt
, čo nie je rozhodne to, čo chceme.
Vylepšiť to môžeme tým, že do xargs
budeme posielať názvy súborov na riadkoch, ale nepomôže to…
ls -1 *.bin | xargs wc -l
… ale bude to krok k správnemu výsledku. xargs
má totiž parameter -I
, ktorým umožňuje určiť presné miesto v príkazovom riadku, na ktoré sa vložia parametre. Štandardne sa totiž parametre dolepujú za príkaz. Ak ich chceme dolepiť na iné miesto, použime toto:
ls -1 *.bin | xargs -I{} wc -l {}
Uff, funguje to, ale… prečo?
Parameter -I
hovorí, že namiesto znakov {}
sa vložia samotné hodnoty pre parametre do príkazového riadku na miesto, ktoré si špecifikujeme sami. (Zástupný znak si môžete zvoliť ľubovoľne, ale konvencia z find
u odporúča použiť práve kombináciu {}
.)
Dôležité je však druhé správanie: -I
hovorí, že hodnoty pre parametre budú na samostatných riadkoch. Príkaz echo *.bin
pchá názvy súborov za seba (oddelené medzerami), ale ls -1
ich naseká práve do riadkov, čo sa nám hodí.
Inak povedané, slovom do parametra sa stane všetko, čo sa nachádza na samostatnom riadku, bez ohľadu na to, či obsahuje medzery, alebo nie.
Ak sa na riadku bude nachádzať
`subor 1.txt`
xargs
ho celé vezme a pošle ako parameter do wc
presne tak, ako keby sa vykonalo volanie
wc -l "subor 1.txt"
Samozrejme, tento trik sa spolieha na fakt, že názov súboru neobsahuje znak nového riadka, ale treba si stanoviť isté hranice, po ktoré budeme náš program opravovať. Ak by to predsa nestačilo, tak sa využije…
find
a xargs
xargs
idú často ruka v ruke s find
om. Hoci tento príkaz má podporu pre -exec
, tá je (ako sme spomenuli vyššie) pomalá.
Mnohokrát sa používa omnoho prehľadnejšie a efektívnejšie:
find . *.sh -type f -maxdepth 1 | xargs wc -l
Porovnajte to s klasickým:
find . *.sh -type f -maxdepth 1 -exec wc -l {} \;
Opäť platí, že to bude fungovať len pre súbory neobsahujúce medzery. GNU find
však na túto možnosť myslel, pretože dáva k dispozícii možnosť vypľuť na výstup súbory oddelené znakom NUL
(teda znakom s ASCII hodnotou 0). Dosiahneme to parametrom -print0
.
xargs
u potom môžeme povedať, že položky v štandardnom vstupe sú oddelené tým istým znakom , stačí použiť parameter -0
.
find . *.sh -maxdepth 1 -print0 | xargs -0 wc -l
Táto možnosť je o niečo dlhšia ako kombinácia ls
a xargs
, ale je o to robustnejšia.
printf
a xargs
Analogická možnosť je dosiahnuteľná cez printf
, kde povieme napevno, že položky vypadnuté z hviezdičkovej expanzie sa majú oddeliť NUL
znakom.
printf "%s\0" *.sh | xargs -0 wc -l
Táto finta stojí a padá na vlastnosti printf
u: ak je špecifikátorov formátu menej než skutočných parametrov pre printf
, posledný sa zopakuje toľkokrát, koľko je treba. V tomto prípade sa pre každý skutočný parameter použije špecifikátor %s\0
, kde \0
indikuje práve NUL
znak.
Ďalšia úloha: Vytvorte 10 súborov s názvom subor1.txt
… subor10.txt
Namiesto for
cyklu vieme použiť kombináciu seq
a xargs
s touch
:
seq 10 | xargs -I{} touch subor{}.txt
Príkaz seq
nageneruje na každý riadok jedno číslo, a kombinácia -I
bude brať jednotlivé riadky a zároveň poslúži na vyformátovanie názvu súboru.
Ak by sme chceli súbory subor 1.txt
atď (s medzerami), použijeme klasické zásady o úvodzovkovaní zo shellu.
seq 10 | xargs -I{} touch "subor {}.txt"
Ďalšie triky s xargs
Sekanie vstupov sa dá použiť na kozmetické efekty:
Vypíšte maticu 3 x 3 s prvkami 1..9
Stačí použiť fintu s dávkovaním parametrom po n-ticiach:
seq 9 | xargs -n3
Výsledkom je
1 2 3
4 5 6
7 8 9
Vypíšte jednotkovú maticu 3 x 3
yes | head -n9 | xargs -n3 | tr 'y' '1'
Príkaz yes
generuje y
donekonečna, ale nám stačí odseknúť head
om prvých 9 hodnôt (3 x 3), ktoré pošleme posekať do xargs
a na záver nahradíme znaky y
jednotkami.
Vypíšte čísla na jeden riadok
Príkaz xargs
vie poslúžiť ako lepič riadkov:
seq 10 | xargs
Výsledok:
1 2 3 4 5 6 7 8 9 10
Vypíšte zoznam používateľov v systéme na jeden riadok
cut -d: -f1 < /etc/passwd | sort | xargs
Vymažte súbory *.txt
Klasické riešenie stavia na rm
a expanzii hviezdičky
rm *.txt
Ak nechceme naraziť na limity dĺžky cesty, pokojne môžeme použiť kombináciu xargs
a rm
echo *.txt | xargs rm
Trasovanie príkazov
Ak chcete vidieť, čo sa naozaj vykoná, spustite xargs
s parametrom -t
.