bash
Bash | |
Bash és Bourne-shell (sh) | |
Fejlesztő | Chet Ramey |
Első kiadás | 1989. június 7. |
Legfrissebb stabil kiadás | 5.2.37 (stabil verzió, 2024. szeptember 23.)[1] |
Programozási nyelv | C |
Operációs rendszer | multi-platform |
Platform | GNU |
Kategória | Unix shell |
Licenc | GNU General Public License |
A Bash weboldala |
A bash unix rendszerhéj, amely a GNU Projekt részeként készült. A futtatható fájl neve bash egy játékos mozaikszó, mely a Bourne again illetve a born again kifejezéseket rövidíti. Ez a korai Bourne shell-re utal, melyet még 1978-ban adtak ki a Unix 7 Verzió részeként. A bash-t 9 évvel ezt követően 1987-ben készítette el Brian Fox, majd 1990-ben Chet Ramey vette át a szoftver gondozását.
A legtöbb Linux rendszeren, valamint az OS X rendszereken a bash az alapértelmezett shell. A Microsoft Windows platformra is átírták a Subsystem for UNIX-based Applications (SUA) használatával, vagy POSIX emuláció biztosításával a Cygwinnel, MSYS-szal.
A bash-t a GNU GPL licenc alatt publikálták, tehát szabad szoftver.
Inicializálás
[szerkesztés]A bash inicializálása attól függ, hogy milyen módban indítják.
Interaktív login mód
[szerkesztés]Ebben az esetben a /etc/profile script kerül először végrehajtásra, ha az létezik. Ezt követően a bash megvizsgálja a ~/.bash_profile, ~/.bash_login és a ~/.profile fájlokat ebben a sorrendben, s lefuttatja közülük az első olyat, amely létezik és olvasható.
Interaktív (nem login) mód
[szerkesztés]Ha a bash interaktív nem login shellként indul, akkor a felhasználó home könyvtárában lévő .bashrc fájlt futtatja, ha az létezik.
Nem interaktív mód
[szerkesztés]Ha a bash-t nem interaktívan futtatják, például shell script-ek végrehajtásához, akkor a BASH_ENV környezeti változóban megadott inicializáló script fog lefutni induláskor.
Restricted (szigorú) mód
[szerkesztés]Ha a bash -t az rbash binárissal, vagy a --restricted kapcsolóval indítják, ezen mód kerül érvényre. A következő funkciók letiltásra vagy korlátozott használatra állnak be:
- Könyvtárak váltása a cd paranccsal
- A következő környezeti változók értékeinek felülírása: SHELL, PATH, ENV, és BASH_ENV
- Parancsok végrehajtása, melyek nevében slash "/" karakter szerepel
- Fájlnév specifikálása amely slash karaktert tartalmaz a . beépített parancshoz
- Fájlnév specifikálása amely slash karaktert tartalmaz a beépített hash parancs `-p' opciójához
- Funkció definíciók importálása a shell környezetből indulás közben
- A SHELLOPTS változó értékének értelmezése a shell környezetből indulás közben
- Kimenetek átirányítása a `>', `>|', `<>', `>&', `&>', és a `>>' parancsokkal
- A beépített exec parancs használata a shell cseréjére
- Beépített parancsok hozzáadása vagy eltávolítása az `-f' és a `-d' opciókkal
- A `-p' opció használata bármely beépített parancshoz
- A szigorú mód kikapcsolása a `set +r' vagy `set +o restricted' parancsokkal
A szigorú módot sok felhasználót kiszolgáló, biztonságosra tervezett rendszerek esetén alkalmazzák.
Speciális karakterek
[szerkesztés]A parancssorba írt információk egy része az utasítást végrehajtó programnak szól, más részük a programot indító bash-nek. Az utóbbiakat speciális karakterekkel jelezzük.
Új sor
[szerkesztés]A legegyszerűbb speciális karakter a parancssort lezáró újsor (soremelés, LF). Hatására a bash
- befejezi a parancs beolvasását és elemzi a parancsot
- elindítja a parancsban megadott programo(ka)t
- megvárja, amíg az véget ér, és csak azután ad lehetőséget újabb parancs begépelésére (a prompt kiírása után).
A harmadik lépés elhagyható egy másik speciális karakterrel. A parancs után írt &
arra utasítja a bash-t, hogy ne várja meg a parancs lefutását (háttérben indított program; lásd még job control). Miután az indított program örökli a szülő nyitott fájljait, és a bash is tovább fut, a két program kimenete összekeveredik (hacsak a gyerekprogram nem módosítja a kapott nyitott fájlokat).
Tipikusan egy sorban egy parancs van, de lehet folytatósort írni (egy parancs több sorban), és többféle módon lehet több parancs egy sorban.
Szóhatár
[szerkesztés]Az utasítás elemzésének egyik legutolsó lépése a szavakra bontás.[2] Az utasítás első szava a végrehajtandó parancs/program neve, a többi szót a program paraméterként kapja meg.
A szóhatárt az IFS nevű környezeti változó tartalmazza, értéke alaphelyzetben helyköz, tabulátor, új sor (LF).[3] Több egymás utáni szóhatár-karakter egy szóhatárnak számít. (Üres szót pl. ""
alakban lehet írni, lásd speciális karakter elrejtése.)
Speciális karakter elrejtése
[szerkesztés]Sokszor van rá szükség, hogy a speciális karaktert a végrehajtandó parancs kapja meg, más szóval: a bash ne kezelje azt speciálisan. Ennek három módja van:
- a
\
az őt követő egyetlen karaktert nem tekinti speciálisnak. A \-jel\\
alakban írható. '
(aposztróf): az összes speciális karaktert elrejti (kivéve a lezáró újabb '-ot)-
"
: az összes speciális karaktert elrejti, kivéve\
(lásd feljebb)$
(hivatkozás környezeti változó vagy aritmetikai kifejezés értékére)`
(parancson belüli parancs végrehajtása; lásd backtick parancs)- " (az elrejtés lezárása)
Az elrejtő karakterek felváltva is használhatók. Pl. a "'
szöveg a bash számára írható '"'"'"
, \"\'
, "\"'"
és számos más alakban.
Speciális eset a sor végi \újsor
, mely a folytatósor jele: a bash eltávolítja a parancssorból, és a következő „fizikai” sorral folytatja a parancs belolvasását.
Wildcard
[szerkesztés]A wildcard olyan karakter, mely lehetővé teszi fájlnévminta megadását:
*
: nulla vagy több karakter a fájlnévben?
: egyetlen karakter a fájlnévben[karakterek]
: a szögletes zárójelben felsorolt karakterek valamelyike. A karakterek helyén intervallum is megadható. Pl.[a-z0-9-]
a kisbetűket, számjegyeket és a kötőjelet jelenti.[^karakterek]
vagy[!karakterek]]
: egyetlen, a szögletes zárójelben felsoroltaktól különböző karakter.
A bash megkeresi a wildcard-ot tartalmazó szóra illeszkedő nem rejtett fájlokat, és ezek listáját helyközzel elválasztva a parancssorba illeszti a wildcard-os szó helyére.[4] Ha nincs ilyen fájl, nem módosít a parancssoron.
A bash számára speciális (nem wildcard) karakter a szó (fájlnév) elején álló tilde (~
), melyet a bash a felhasználó saját (HOME) könyvtárára helyettesít.
A bash számára a ponttal kezdődő nevű fájlok rejtettek: a wildcard kiterjesztésekor nem kerülnek a listába, kivéve, ha a wildcard-os fájlnév ponttal kezdődik. Minden könyvtárban van két rejtett fájl: a .
az aktuális, a ..
a felette levő könyvtár neve. Ezek szükségesek a könyvtárszerkezetbeli navigáláshoz.
A fentiekből következik, hogy a Unix-programoknak nem kell felkészülniük a wildcard kezelésére, hiszen ezeket a shell – e szócikkben a bash – elvégzi, ráadásul egységes módon.[5] Ehelyett tetszőleges számú fájlt kell tudniuk feldolgozni. A hívott program nem is tudja, hogy a fájl-paramétereit wildcard-dal vagy a fájlok felsorolásával kapta-e.
Példák wildcard-ra:
*
: a könyvtár összes nem rejtett fájlja*.*
: legalább egy pontot tartalmazó fájlnév*\ *
: legalább egy helyközt tartalmazó fájlnév (a\
jelentését lásd feljebb)[A-Z]*
: nagybetűvel kezdődő fájlnév*.c
: .c kiterjesztésű fájlnév[6]
Környezeti változók
[szerkesztés]A kernel a processz (program) adatai között nyilvántart egy memóriaterületet, melyben név=érték
típusú adatok vannak. Amikor egy program elindít egy másik programot,[7] az indított (gyerek)program örökli a szülő éppen aktuális környezeti változóit. A másik lehetőség, hogy a szülő állítja elő a gyerekprogram környezeti változóit, és egy memóriaterületen átadja a gyerekprocesszt indító rendszerhívásban.
A bash mindig az utóbbi módon indít programot. Ez lehetővé teszi, hogy egyes környezeti változói helyiek legyenek, mások az indított program környezetébe is bekerüljenek.
A bash indulásakor már vannak környezeti változói az őt indító programtól (pl. a bejelentkezési eljárásból), és a bash többféle indító scriptje is létrehoz változókat. A bash lehetővé teszi a változók lekérdezését, módosítását, törlését, újak létrehozását.
Fontos tudni, hogy Unixban minden program – a bash is – csak a saját környezeti változóit tudja megváltoztatni, a szülőjéét nem, ui. annak csak a másolatát kapja meg.
A bash környezeti változói az env
utasítással, az indított programoknak továbbadottak a set
utasítással listázhatók ki. A két listában a nevek és értékek is szerepelnek.
A környezeti változó értékét a $név
vagy ${név}
kifejezés adja meg. A két alak egyenrangú; a másodikat akkor használjuk, ha a változót el akarjuk választani az őt követő betűtől vagy számtól (ami az első alakban összeolvadna névvel). Az értékre hivatkozást általában idézőjelbe teszünk, bár ez nem mindig szükséges. Az értéket a echo "$név"
utasítás írja a képernyőre. Nem hiba értéket nem kapott változót használni; ennek értéke üres string.
Új helyi változót létrehozni, vagy a meglevőt módosítani a név=érték
utasítással lehet. Az egyenlőségjel előtt és után nem lehet helyköz. Az értékben szerepelhet a változó régi értéke.
Ha azt szeretnénk, hogy a változót az indított programok is lássák, az export név
utasítást használjuk. A név után az érték is megadható.
Változó az unset név
utasítással törölhető.
A legfontosabb környezeti változók
[szerkesztés]- HOME: a bejelentkezett felhasználó saját könyvtára.
- IFS: szóhatár.
- PATH: könyvtárak kettősponttal elválasztott listája. Itt keresi a kernel az indítandó programot, ha annak útvonala nincs megadva. Az aktuális könyvtár a Dos/Windows rendszerektől eltérően alaphelyzetben nincs a PATH-ban, és biztonsági okból nem is tanácsos betenni, különösen a mindenható root felhasználó esetén.
- PS1, PS2, PS3, PS4: a prompt szövegét megadó változók (normál prompt, folytatósor prompt, a select utasítás promtja, debug prompt).
- SHELL: a shell neve; e szócikkben /bin/bash.[8]
Példák
[szerkesztés]A PATH kibővítése a felhasználó könyvtárának bin alkönyvtárával:
export PATH=$PATH:$HOME/bin
Ugyanez leírható
export PATH+=:$HOME/bin
alakban is.
Átirányítás
[szerkesztés]Amikor Unixban egy program elindít egy másikat, a gyerekprogram – a környezeten kívül – a nyitott fájlokat is örökli. A felhasználó bejelentkezési procedúrája során több program is lefut, és ennek során létrejön három nyitott fájl azon az eszközön (terminálon, soros vonalon, stb.), ahonnan a felhasználó bejelentkezett, így a felhasználó login shellje ezeket már megnyitva kapja. A fájlok a megnyitás sorrendjében kapnak számot a kerneltől:
- 0: standard input
- 1: standard output
- 2: standard hiba.
Az átirányítás célja a, hogy az indított program ne a bash-től örökölje a fenti fájlokat, hanem a parancssorban megadott fájlokat kapja meg. A három fájl egymástól függetlenül irányítható át.
A standard input átirányítása
[szerkesztés]Az egyik mód az indított program standard inputjának megadása:
program <fájl
A másik mód shell scriptben használatos: az indított program az indító scriptből vegye a standard inputot:
program <<szó program stdin-jének első sora második sor ... szó
A „fájlt” lezáró szó a sor elején kell legyen. Ha a hívott program végigolvassa az standard inputját, a záró szó helyett fájl végét kap. Ha nem olvassa végig, a következő utasítás előtt a bash átugorja a be nem olvasott részt.
A kimenetek átirányítása
[szerkesztés]A standard kimenet átirányítása:
program >fájl program >>fájl
Mindkét alak létrehozza fájl-t, ha az nem létezett a parancs kiadásakor. Az első a létező fájl tartalmát törli, de a jogait nem változtatja. A második a fájl vége után írja program kimenetét.
Fontos tudni, hogy az átirányítás a program indítása előtt történik (lásd alább). Az első alakban fájl tartalma akkor is megsemmisül, ha a hívott program nem is létezik.
A standard hiba átirányítása teljesen hasonló:
program 2>fájl program 2>>fájl
A bash standard kimenetére &1
, a hibakimenetre &2
alakban hivatkozhatunk.[9]
Egy program mindkét kimenete ugyanabba fájlba irányítható:
program >fájl 2>&1
Egy program indítása két lépésben történik. Első lépés a fork, melynek során az indító bash teljes memóriája és processztáblája megduplázódik, azaz a bash két külön környezetben (szülő és gyerek) fut tovább. A második lépés a program kódjának betöltése a gyerek-bash helyére, és az újonnan betöltött kód elindítása.
Az átirányítás a fork fázisban történik, hogy a bash eredeti környezete ne változzék.[10] A fenti példában a >fájl
fájl újranyitás az 1-es számú fájlleírón, vagyis egy rendszerhívás, mely bezárja az eredeti fájlt, és a helyére megnyit egy másikat. A 2>&1
az 1-es fájlleíró duplikálása a 2-es leíróba. Ez azt is jelenti, hogy a fenti sorrend nem felcserélhető. A 2>&1 >fájl
az eredeti fájlleírót duplikálja, így az újranyitás már nem hat a hibakimenetre.
A fenti kettős átirányítás egy utasítással is végrehajtható:
program &>fájl
Gyakran használatos a kimenet átirányítására a /dev/null
speciális eszközfájl, mely „elnyeli” – eldobja – az oda küldött adatokat.
Parancsok kombinálása
[szerkesztés]Pipe
[szerkesztés]prog1 | prog2
hatására prog1 és prog2 között névtelen pipe jön létre.[11] A két program külön-külön processzként indul el úgy, hogy prog1 standard kimenete átirányítódik prog2 standard bemenetére. A bash megvárja mindkét program lefutását, és prog2 visszatérési értékét teszi a $? változóba. Kettőnél több program is összeköthető pipe-pal.
Backtick
[szerkesztés]Alakja:
parancs1 ... `parancs2...` ... parancs1 ... $(parancs2...) ...
Először parancs2 hajtódik végre, és a standard kimenete behelyettesítődik parancs1 parancssorába (a paraméterek közé). Az újsor[12] karakterek helyközre cserélődnek.
A fenti két alak annyiban különbözik egymástól, hogy a backtick-ek nem skatulyázhatók egymásba, a $(...)-k igen.
Példa:
file `which ls`
A which ls
parancs végigkeresi a PATH környezeti változót, és visszaadja az ls parancs fájljának útvonalát. A file parancs kiírja a parancs típusát:
$ file `which ls` /bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), ...
;
[szerkesztés]A pontosvesszővel elválasztott parancsok egymás után futnak le. Az utolsó parancs visszatérési értéke kerül a $? változóba.
Példa: fájlt másolunk egy másik ablakban, és közben figyeljük a diszk telítettségét:
while true ; do df -hT ; sleep 30 ; done
&&
[szerkesztés]A && bal oldalán levő parancs lefutása után akkor hajtódik végre a jobb oldali, ha a bal sikeres volt (a visszatérési értéke 0).
Példa: a make paranccsal lefordítjuk a prog programot, és ha a fordítás sikerült, végrehajtjuk:
make && ./prog
||
[szerkesztés]A jobb oldali parancs akkor hajtódik végre, ha a bal oldali sikertelen volt (nem 0 a visszatérési értéke):
make || exit 2
Kerek zárójel
[szerkesztés]A kerek zárójelbe tett parancsok egy shellben futnak le. Ez pl. akkor lehet hasznos, ha egy fájlba akarjuk irányítani a kimenetüket, vagy egy processzben akarjuk őket futtatni háttérben. A zárójelet helyközzel kell elválasztani a parancs többi részétől.[13] A parancsok több sorba is írhatók.
Shell script
[szerkesztés]A script (egyre gyakrabban írják magyarosan szkriptnek) Unixban olyan fájl, mely egy interpretált programozási nyelv utasításait tartalmazza. Interpreterek a Unix shellek – köztük a bash – is.
Script hívása
[szerkesztés]A bash a shell scriptet ugyanúgy indítja el, mint a bináris programokat. Ez azt jelenti, hogy a script másik shellben fog futni.[14] Ez általában kényelmes, viszont a hívott shell nem tudja megváltoztatni a hívó környezetét (így a környezeti változókat sem). Ennek megoldására szolgál a pont parancs:
. shell-script source shell-script
A két alak egyenértékű, és a script neve után paraméterek is megadhatók. A parancs hatására a bash nem indít külön programot, hanem ő maga hajtja végre a megadott shell scriptet.
A hívott script (vagy más program) visszatérési értéke a $?
shell-változóba kerül. A hívott script az exit
utasítás paraméterében állíthatja be a visszaadandó értéket.
Az első sor
[szerkesztés]A program indításakor a kernel[7] „belenéz” az indítandó fájl elejébe, és ebből állapítja meg az indítás módját.[15] Script esetén a fájl a #!interpreter
sorral kezdődik.[16] Az interpreter nevét az új sor (LF) karakter zárja le. (Ügyeljünk rá, hogy ne legyen előtte CR, mert az is a név része lenne.) A kernel az interpretert indítja el, első paraméterként átadja a scriptfájlt. Az interpreter a többi paraméterét a hívósorból kapja, hogy átadhassa a scriptnek.
A bash-script első sora Linuxban:[8]
#!/bin/bash
A scripteket a /bin/sh
sorral szokás kezdeni, ami a Bourne shellt hívja.[17] A bash a Bourne shell továbbfejlesztett változata; ha szükség van a többlet-utasításokra (pl. tömbökre), akkor az előbbi formát kell használni.
Paraméterek
[szerkesztés]A script a saját fájlnevét (útvonallal együtt) a $0
változóban kapja, a többit a $1
...$9
változóban. Tíznél több paraméter a beépített shift
utasítással vehető át. A kapott paraméterek számát a $#
változó tartalmazza.
Scriptnyelv
[szerkesztés]Bár a shell elsősorban külső programok hívására szolgál, saját beépített utasításai is vannak, melyek önálló programnyelvet alkotnak. A harmadik generációs programnyelvek[18] szokásos utasításaira példák:
- deklaráció: export.
- értékadás: lásd környezeti változók, aritmetikai kifejezések
- vezérlő utasítások: if, case, for, while, until, test
- I/O utasítások: cd, pwd, echo, printf, read
- függvények
- processzek: lásd job control, szignálok.
Bash függvények
[szerkesztés]Bash-függvény definiálása:
fuggveny() { ... }
Hívás: fuggveny par1 par2...
. Függvényen belül a $1
...$9
változó nem a shell script, hanem a függvény paraméterét jelenti. A függvény a return kód
utasítással adhat vissza számértéket.
Aritmetikai kifejezések
[szerkesztés]Egész aritmetikai kifejezés $(( ... ))
alakban írható. A műveletek azonosak a C nyelvbeliekkel. Az operandusok környezeti változók is lehetnek.
Példa:
HAROM=$((1+2))
Háttérben indított programok
[szerkesztés]Program háttérben indításakor kiíródik a processz száma, melyet a script változóba tud tenni. A script a wait procszám
utasítással várhatja meg a program lefutását. A paraméter nélküli wait az összes háttérbeli programot megvárja.
Job control
[szerkesztés]A jobs
utasítás kilistázza a shellből indított, még le nem futott programokat, akár eleve háttérben indítottuk őket, akár utólag, kézzel állítottuk meg és/vagy tettük háttérbe (lásd alább). A program akkor minősül „lefutott”-nak, ha befejeződött a végrehajtása, és ezután lekérdezték a státusát.[19] A lekérdezést elvégzi a jobs, de maga a bash is, mielőtt kiírja a következő utasítás promptját.
Mialatt a bash az indított program lefutására vár, a Ctrl/Z
megszakítja a várakozást (SIGTSTP szignál), megállítja az indított programot, és visszaadja a promptot. A bg szám
háttérben, a fg szám
előtérben indítja tovább a programot, ahol szám a jobs által kiírt job sorszám.
A Ctrl/C (SIGINT szignál) befejezi az előtérben futó programot. Ha a bash fut, Ctrl/C-re eldobja az addig beolvasott sort, és új promptot ír ki.
Szignálok
[szerkesztés]A szignál segítségével az egyik Unix processz értesítheti a másikat egy esemény bekövetkeztéről. 64-féle szignál van, melyek különböző eseményeket jelezhetnek. Ha a programot nem úgy írták meg, hogy képes legyen szignált fogadni, a szignáltól függő default akció történik a szignál címzettjével:
- befejeződik a futása
- figyelmen kívül marad a szignál
- a processzről core dump készül
- a processz futása megáll
- a processz futása újraindul.
A SIGKILL (9-es) szignál nem jut el a címzetthez: a kernel kilövi a címzett processzt (feltéve, hogy a küldőnek erre volt joga). Ha egy program elszabadul, és már szignálokra sem reagál, ez az egyetlen módja a program leállításának. Hátránya, hogy a program nem tud rendet tenni a befejeződése előtt (pl. a puffereit kiüríteni).
bash-ban a kill -l
beépített parancs kilistázza a szignálok nevét és kódját.
Szignál küldése bash-ből
[szerkesztés]Szignál küldése:
kill -szám procid...
ahol szám a szignál száma vagy neve (ha elmarad, 2 = SIGTERM), melyet processzazonosítók helyközzel elválasztott listája követ. A név arra utal, hogy legtöbb esetben programok kilövésére használjuk a parancsot. A létező processzazonosítókat pl. a ps
és pgrep
parancs írja ki.
A killall és pkill parancs lehetővé teszi szignál küldését adott nevű és/vagy adott felhasználóhoz tartozó processzeknek.
Shell script a trap
utasítással fogadhat szignálokat.
Jegyzetek
[szerkesztés]- ↑ https://ftp.gnu.org/gnu/bash/
- ↑ A parancssor utasításokra bontása korábbi lépésekben történik.
- ↑ Így lehet kiíratni:
echo -n "$IFS" | hexdump -C
- ↑ A bash parancssorának hossza a kerneltől függ, alaphelyzetben 128 kByte.
- ↑ Néhány speciális program, pl. az ls és a find saját fájlkeresést használ.
- ↑ Unixban nincs fájl kiterjesztés: e szó a DOS-os terminológiából származik. A pont nem különleges karakter a fájlnévben.
- ↑ a b Lásd exec rendszerhívás-családot.
- ↑ a b Egyes Unixokban a bash a /usr/bin könyvtárban van.
- ↑ Ha a bash-ben további fájlokat nyitottunk meg, azok fájlszáma is írható ilyen alakban.
- ↑ A fork fázis célja éppen az indítandó program környezetének beállítása a szülő memóriaterületén levő adatokból.
- ↑ Névvel rendelkező pipe a
mkfifo
paranccsal hozható létre. - ↑ Pontosabban: az IFS környezeti változó karakterei.
- ↑ A helyköz nélküli listát egy tömb elemeinek tekinti a bash.
- ↑ A Dos/windowsos rendszerekben ez éppen fordítva van. A shell a kiterjesztésből tudja, hogy shell scriptet kell hívnia, és azt ugyanazzal a shellel hajtja végre, hacsak nem használjuk a call beépített utasítást.
- ↑ A file paranccsal kiíratható az információ.
- ↑ Ez azt jelenti, hogy a script-nyelvek a
#
-sel kezdődő sorokat – legalábbis az első sorét – kommentnek kell tekintsék. - ↑ Linuxban a Bourne shellt is a bash hajtja végre szűkített módban futva.
- ↑ Lásd programnyelv-generációk(en).
- ↑ Ha a program lefutott, de a szülő még nem kérdezte le a státusát, a program a memóriában marad. Az ilyen processzt nevezik zombinak(en).
Források
[szerkesztés]- Bash Reference Manual (gnu.org)