Linux-ի հիմունքներ՝ shell, ֆայլեր և pipe-եր
Ֆայլային համակարգը, ամեն օր գործածվող հրամանները և ինչպես դրանք իրար միացնել։
30 վայրկյանում
Linux-ը այն օպերացիոն համակարգն է, որի վրա աշխատում են գրեթե բոլոր վեբ սերվերները։ Դու նրա հետ խոսում ես shell-ի միջոցով՝ գրում ես հրաման, այն գործարկում է փոքր ծրագիր և տեքստ է վերադարձնում։ Սովորիր, թե որտեղ են գտնվում ֆայլերը, մեկ տասնյակ ամենօրյա հրամաններ և ինչպես են pipe-երը դրանք շղթայում, ու սերվերի խնդիրների մեծ մասը կկարողանաս ինքնուրույն գտնել և շտկել։
Ինչու եմ օգտագործում
- Linux-ը օգտագործում եմ ամեն շաբաթ․ իմ սեփական պրոդուկտների և հաճախորդների համար իմ աջակցած բոլոր սերվերները Ubuntu մեքենաներ են, որոնք կառավարում եմ SSH-ով։
- Երբ production-ը կոտրվում է, պատասխանը գրեթե միշտ log ֆայլում կամ config ֆայլում է։ Shell-ը լավ իմանալն է, որ թույլ է տալիս այն գտնել րոպեների, ոչ թե ժամերի ընթացքում։
- Shell-ը միակ գործիքն է, որ նույն կերպ է աշխատում էժան VPS-ի վրա, Docker կոնտեյների ներսում և CI pipeline-ում, ուստի դրա վրա ծախսած ժամանակը ամենուր է իրեն արդարացնում։
- grep-ի, sort-ի և uniq-ի նման փոքր pipeline-երը պատասխանում են հարցերի, որոնց համար այլապես script կամ dashboard կպահանջվեր։
Ինչպես է աշխատում
01Ի՞նչ են Linux-ը և դիստրիբուտիվը
Խիստ ասած՝ Linux-ը միայն kernel-ն է՝ միջուկային ծրագիրը, որը խոսում է hardware-ի հետ, պլանավորում է պրոցեսները և կառավարում հիշողությունը։ Դիստրիբուտիվը (distro) այդ kernel-ը միավորում է package manager-ի, համակարգային գործիքների, լռելյայն կոնֆիգուրացիայի և թողարկումների ժամանակացույցի հետ։ Սերվերների վրա հիմնականում կհանդիպես երկու ընտանիքի։ Debian ընտանիքը (Debian, Ubuntu) օգտագործում է .deb փաթեթներ և apt։ Red Hat ընտանիքը (RHEL, Rocky Linux, AlmaLinux, Fedora) օգտագործում է .rpm փաթեթներ և dnf։ Սերվերների համար ընտրիր երկարաժամկետ աջակցությամբ (LTS) թողարկում․ Ubuntu-ի LTS տարբերակները դուրս են գալիս երկու տարին մեկ և ստանում են հինգ տարվա ստանդարտ անվտանգության թարմացումներ, Debian stable-ը LTS ընդլայնմամբ ստանում է մոտ հինգ տարի, իսկ RHEL-համատեղելի թողարկումները՝ մոտ տասը։ Այս դասի գրեթե ամեն ինչ նույն կերպ է աշխատում բոլոր դիստրիբուտիվներում․ հիմնական տարբերությունները փաթեթների անուններն են, package manager-ը և մի քանի config ֆայլերի տեղը։
- Kernel՝ ՕՀ-ի միջուկը։ Distro՝ kernel գումարած գործիքներ, փաթեթներ և լռելյայն կարգավորումներ։
- Debian / Ubuntu՝ apt և .deb։ RHEL / Rocky / Alma՝ dnf և .rpm։
- Shell՝ ծրագիրը, որը կարդում է քո հրամանները։ Սերվերների մեծ մասում լռելյայնը Bash-ն է։
02Ֆայլային համակարգի հիերարխիան՝ ինչը որտեղ է
Linux-ն ունի դիրեկտորիաների մեկ ծառ, որը սկսվում է /-ից (root)։ Սկավառակների տառեր չկան․ լրացուցիչ սկավառակները միացվում են (mount) այդ ծառի ներսում ինչ-որ տեղ։ Կառուցվածքը հետևում է Filesystem Hierarchy Standard կոչվող ստանդարտին, ուստի մեկ անգամ սովորելուց հետո կկողմնորոշվես ցանկացած սերվերում։ Օգտակար կանոն՝ կոնֆիգուրացիան /etc-ում է, այն, ինչ փոխվում է համակարգի աշխատանքի ընթացքում (log-եր, cache, տվյալների բազաներ, կայքեր), /var-ում է, ծրագրերը՝ /usr-ում, իսկ մարդիկ՝ /home-ում։ Ամեն ինչ ֆայլ է, ներառյալ սարքերը (/dev) և kernel-ի կենդանի տեղեկությունը (/proc, /sys), և հենց դրա համար են այդքան շատ գործիքներ պարզապես տեքստ կարդում ու գրում։
- /etc՝ համակարգի և ծառայությունների կոնֆիգուրացիա (nginx, ssh, cron, օգտատերեր)։
- /var՝ փոփոխվող տվյալներ։ /var/log՝ log-երի համար, /var/www՝ ընդունված կարգով կայքերի համար, /var/lib՝ տվյալների բազաների և հավելվածների վիճակի համար։
- /usr՝ տեղադրված ծրագրեր և գրադարաններ։ /usr/local՝ ձեռքով տեղադրվածների համար։
- /home/your_user՝ անձնական ֆայլեր։ /root-ը root օգտատիրոջ home-ն է։
- /tmp՝ ժամանակավոր ֆայլեր, որոնք հաճախ մաքրվում են reboot-ի ժամանակ։ /opt՝ ոչ պարտադիր, ինքնաբավ հավելվածներ։
- /dev, /proc, /sys՝ սարքեր և kernel-ի ու պրոցեսների կենդանի տեղեկություն։
03Նավիգացիա և ֆայլերի կառավարում
Մի քանի հրաման ծածկում է ամենօրյա աշխատանքի մեծ մասը։ pwd-ն ցույց է տալիս, թե որտեղ ես, cd-ն տեղափոխում է, ls-ը ցուցակում է։ /-ով սկսվող ճանապարհները բացարձակ են, մնացածը հարաբերական են ընթացիկ դիրեկտորիայի նկատմամբ։ Մեկ կետը նշանակում է «այստեղ», երկու կետը՝ ծնող դիրեկտորիան, իսկ tilde-ն՝ քո home-ը։ Զգույշ եղիր rm-ի հետ․ աղբարկղ չկա, և rm -r-ը առանց հարցնելու ջնջում է ամբողջ ծառը։ Լավ սովորություն է նախ ls գործարկել ճիշտ նույն ճանապարհի վրա և երբեք չտեղադրել rm հրաման այնպիսի փոփոխականով, որը չես ստուգել։ find-ը շվեյցարական դանակ է ֆայլերը ըստ անվան, չափի, տարիքի կամ տիպի գտնելու համար։
pwd # where am I?
ls -lah /var/log # long list, all files, human sizes
cd /etc/nginx && ls # move, then list
cd ~ ; cd - # go home, then back to the previous dir
mkdir -p ~/backups/2026 # create a path, including parents
cp -a site/ site-backup/ # copy recursively, keep owners and times
mv old.conf old.conf.bak # move or rename
rm -i notes.txt # delete, asking first
rm -r build/ # delete a directory tree (no undo!)
find /var/www -name "*.log" -size +100M # big log files
find /tmp -type f -mtime +7 # files older than 7 days
04Ֆայլերի ընթերցում, pipe-եր և վերաուղղորդում
cat-ը տպում է ամբողջ ֆայլը, less-ը թույլ է տալիս ֆայլը թերթել և դրանում որոնել (/՝ որոնելու, q՝ դուրս գալու համար), head-ը և tail-ը ցույց են տալիս սկիզբը կամ վերջը, իսկ tail -f-ը շարունակում է հետևել ֆայլին, երբ նոր տողեր են ավելանում, ինչը կատարյալ է log դիտելու համար, մինչ bug-ը վերարտադրում ես։ Իրական ուժը pipe-երի մեջ է։ Յուրաքանչյուր ծրագիր ունի standard input, standard output և standard error։ | նշանը մի ծրագրի output-ը ուղարկում է հաջորդի input-ին, և փոքր գործիքները միավորվում են պատասխանների մեջ։ Վերաուղղորդումը output-ը ուղարկում է ֆայլեր․ >-ը վերագրում է, >>-ը ավելացնում է վերջում, իսկ 2>-ը սխալները առանձին է հավաքում։ 2>&1-ը սխալները միացնում է սովորական output-ին։
less /var/log/syslog # scroll and search
tail -n 100 -f /var/log/nginx/error.log # follow live
# Top 10 client IPs in an access log
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
# Save output, append to a file, capture errors
df -h > disk.txt
date >> deploy.log
./deploy.sh > deploy.out 2>&1
# Count 500 errors
grep -c '" 500 ' /var/log/nginx/access.log
05grep, awk և sed հինգ րոպեում
Այս երեք գործիքները լուծում են տեքստային խնդիրների մեծ մասը։ grep-ը գտնում է pattern-ին համապատասխանող տողերը․ -i-ն անտեսում է մեծատառ/փոքրատառը, -r-ը որոնում է դիրեկտորիաներում, -n-ը ցույց է տալիս տողերի համարները, -v-ն շրջում է համընկնումը, իսկ -E-ն միացնում է extended regular expression-ները։ awk-ը յուրաքանչյուր տող բաժանում է դաշտերի ($1, $2…) և իդեալական է log-երից սյուներ հանելու համար։ sed-ը խմբագրում է տեքստի հոսքեր, ամենից հաճախ՝ search and replace-ի համար։ sed-ը միշտ նախ գործարկիր առանց -i-ի՝ արդյունքը նախադիտելու համար, իսկ երբ տեղում ես խմբագրում, պահիր backup -i.bak-ով։ awk-ի կամ sed-ի մեկ տողից երկար ցանկացած բանի համար իրական լեզվով փոքր script-ը սովորաբար ավելի պարզ է։
grep -rn "DB_HOST" /var/www/app/config # where is it set?
grep -i "error" app.log | grep -v "deprecated"
grep -E "timeout|refused" app.log
awk -F: '{print $1}' /etc/passwd # first column, ":" separator
awk '$9 >= 500 {print $7}' access.log # paths that returned 5xx
sed 's/http:/https:/g' links.txt # preview a replacement
sed -i.bak 's/^#Port 22/Port 22/' my.conf # edit in place, keep .bak
06Խմբագրիչներ, help և environment փոփոխականներ
Սերվերի վրա ֆայլերը խմբագրում ես տերմինալում։ nano-ն ամենահարմարն է․ shortcut-ները նշված են ներքևում (Ctrl+O՝ պահպանել, Ctrl+X՝ դուրս գալ)։ vim-ը ամենուր է, և արժե այն իմանալ գոնե «գոյատևման» մակարդակով․ սեղմիր i՝ տեքստ ներմուծելու համար, Esc՝ կանգնելու համար, հետո :wq՝ պահպանել և դուրս գալ, կամ :q!՝ դուրս գալ առանց պահպանելու։ Երբ մոռանում ես flag-ը, օգտագործիր command --help՝ կարճ ամփոփման համար, կամ man command՝ ամբողջական ձեռնարկի համար։ Environment փոփոխականները անվանված արժեքներ են, որոնք ծրագրերը ժառանգում են shell-ից, օրինակ PATH-ը (որտեղ են որոնվում հրամանները) և HOME-ը։ Ընթացիկ session-ի համար փոփոխական սահմանիր export-ով, մշտական դարձրու ~/.bashrc-ում։ Հավելվածները secret-ները պետք է կարդան environment փոփոխականներից կամ env ֆայլից, երբեք՝ կոդից։
nano /etc/hosts # simple editor
vim /etc/hosts # i = insert, Esc, :wq = save & quit
ls --help | less # quick help
man rsync # full manual (q to quit)
echo $PATH # where commands are found
export APP_ENV=production
printenv | grep APP_ # show matching variables
which node # which binary runs?
07Քո առաջին shell script-ը
Shell script-ը հրամաններով տեքստային ֆայլ է, որը կատարվում է վերևից ներքև։ Առաջին տողը (shebang) ասում է, թե որ interpreter-ն օգտագործել։ Յուրաքանչյուր bash script սկսիր set -euo pipefail-ով․ -e-ն կանգնեցնում է առաջին ձախողված հրամանի վրա, -u-ն չսահմանված փոփոխականները սխալ է համարում, իսկ pipefail-ը pipeline-ը ձախողված է դարձնում, եթե դրա որևէ մաս ձախողվի։ Առանց դրանց script-ը կարող է ձախողումից հետո շարունակել և վնաս տալ, օրինակ՝ ջնջել ֆայլեր սխալ դիրեկտորիայում, որովհետև փոփոխականը դատարկ էր։ Փոփոխականները չակերտների մեջ վերցրու, ֆայլը կատարելի դարձրու chmod +x-ով և, եթե կարող ես տեղադրել, ստուգիր shellcheck-ով։
#!/usr/bin/env bash
set -euo pipefail
# Archive a directory with today's date
SRC="/var/www/app/storage"
DEST="$HOME/backups"
STAMP="$(date +%F)"
mkdir -p "$DEST"
tar -czf "$DEST/storage-$STAMP.tar.gz" -C "$SRC" .
echo "Saved $DEST/storage-$STAMP.tar.gz"
# Delete archives older than 14 days
find "$DEST" -name "storage-*.tar.gz" -mtime +14 -delete
Օգտագործեք, երբ
- Ցանկացած սերվերի, VPS-ի կամ կոնտեյների կառավարում․ shell-ը ընդհանուր ինտերֆեյսն է։
- Production խնդիրների հետաքննություն․ log-երը, config ֆայլերը և աշխատող պրոցեսները բոլորը տեքստ են։
- Կրկնվող առաջադրանքների ավտոմատացում կարճ script-երով, օրինակ՝ backup-ներ, մաքրումներ և deploy-ի քայլեր։
- Արագ հարցեր log-երի վերաբերյալ (top IP-ներ, սխալների քանակ)՝ առանց dashboard կառուցելու։
Մի՛ օգտագործեք, երբ
- Մեծ կամ բարդ ավտոմատացում․ երբ script-ը մեկ էջից մեծանում է, անցիր Python-ի, Node-ի կամ Ansible-ի նման գործիքի։
- Production config-ի կուրորեն խմբագրում․ նախ պատճենիր այն և վավերացրու (օրինակ՝ nginx -t) մինչև reload անելը։
- Ամենօրյա desktop աշխատանք, որտեղ GUI-ն պարզապես ավելի արագ է․ ամեն ինչ տերմինալում անելու համար մրցանակ չեն տալիս։
Այլընտրանքներ
- zsh / fish
Ավելի հարմար ինտերակտիվ shell-եր՝ ավելի լավ autocomplete-ով։ Լավ են laptop-ի համար, բայց սերվերային script-երը միևնույն է գրիր bash-ով կամ POSIX sh-ով։
- Python կամ Node script-եր
Ավելի լավն են տրամաբանության, JSON-ի, API-ների և սխալների մշակման համար, երբ առաջադրանքը մի քանի հրամանից մեծ է։
- Ansible
Սերվերի կարգավորումը նկարագրում է կրկնվող playbook-ներով՝ ձեռքով գրվող միանգամյա հրամանների փոխարեն։
- Hosting-ի կառավարման պանելներ
Plesk-ի կամ cPanel-ի նման պանելները shell-ը թաքցնում են UI-ի հետևում։ Հարմար է, բայց երբ ինչ-որ բան կոտրվում է, shell-ը միևնույն է պետք է։
Լավագույն պրակտիկաներ
- Մինչև rm անելը ճանապարհի վրա գործարկիր ls, և խուսափիր rm -rf-ից չստուգված փոփոխականներով։
- Խմբագրելուց առաջ config ֆայլի backup արա (cp file file.bak) և վավերացրու այն մինչև ծառայությունը reload անելը։
- Մեծ ֆայլերի համար cat-ի փոխարեն օգտագործիր less և tail -f․ 2 GB log-ի վրա cat-ը կողողի տերմինալդ։
- Script-երը սկսիր set -euo pipefail-ով և փոփոխականները միշտ չակերտների մեջ վերցրու՝ "$VAR"։
- Script-երում և cron job-երում նախընտրիր բացարձակ ճանապարհներ․ դրանք կախված չեն ընթացիկ դիրեկտորիայից։
- Սովորիր man-ը և --help-ը․ դրանք ավելի արագ և ճշգրիտ են, քան կիսով չափ հիշված blog post-երը։
- Secret-ները պահիր environment փոփոխականներում կամ պաշտպանված env ֆայլերում, ոչ թե script-երում կամ shell history-ում։
Տարածված սխալներ
- rm -rf $DIR/ գործարկել դատարկ փոփոխականով, ինչը վերածվում է rm -rf /-ի և ջնջում է համակարգը։
- Օգտագործել >, երբ նկատի ուներ >>, և ֆայլը վերագրել՝ վերջում ավելացնելու փոխարեն։
- Ֆայլը խմբագրել sed -i-ով՝ առանց փոփոխությունը նախադիտելու կամ backup պահելու։
- Մոռանալ, որ Linux-ում ճանապարհներն ու ֆայլերի անունները զգայուն են մեծատառերի նկատմամբ․ Config.php-ն config.php չէ։
- Ինտերնետից հրամաններ root-ով տեղադրել՝ առանց հասկանալու, թե ամեն մասն ինչ է անում։
Խորը վերլուծություն
08Ինչ է իրականում անում shell-ը, երբ սեղմում ես Enter
Մինչև որևէ ծրագրի գործարկվելը bash-ը վերաշարադրում է տողը ֆիքսված հերթականությամբ՝ brace expansion, tilde expansion, հետո փոփոխականների, հրամանների և թվաբանական արտահայտությունների փոխարինում, հետո word splitting, հետո ֆայլերի globbing և վերջում չակերտների հեռացում։ Միայն դրանից հետո է այն փնտրում հրամանը՝ սկզբում alias-ները, ֆունկցիաները և builtin-ները (cd, export, read), հետո PATH-ի յուրաքանչյուր դիրեկտորիան, իսկ արդյունքները պահվում են hash աղյուսակում։ Արտաքին ծրագրի համար shell-ը fork է անում իր պատճենը, և երեխա պրոցեսը exec-ով իրեն փոխարինում է ծրագրով․ ծնողը սպասում է և exit status-ը պահում $?-ում։ Սա բացատրում է դասական անակնկալները՝ cd-ն պետք է builtin լինի, քանի որ երեխա պրոցեսը չի կարող փոխել ծնողի դիրեկտորիան, բացատներ պարունակող առանց չակերտի փոփոխականը դառնում է մի քանի արգումենտ, իսկ ոչնչի չհամընկնող glob-ը փոխանցվում է բառացիորեն։ type-ը և command -v-ն ճշգրիտ ասում են, թե ինչ է գործարկվելու։
type cd ls grep # builtin, alias or file?
command -v python3 # path that will be executed
hash -r # forget cached paths after moving a binary
false; echo $? # 1: last exit status
f="my file.txt"
ls $f # WRONG: two arguments, "my" and "file.txt"
ls "$f" # right: one argument
set -x # print each command after expansion (debug)
09Inode-ներ, hard link-եր և symlink-եր
Ֆայլի անունը ֆայլը չէ։ Տվյալներն ու մետատվյալները (սեփականատեր, mode, ժամանակային նշումներ, չափ, բլոկների ցուցիչներ) պահվում են inode-ում, որը նույնականացվում է համարով, իսկ դիրեկտորիան պարզապես աղյուսակ է, որն անունները կապում է inode համարների հետ։ Hard link-ը նույն inode-ի երկրորդ անունն է, ուստի երկու անուններն էլ հավասար են, և տվյալները մնում են, քանի դեռ վերջին link-ը չի ջնջվել, և ոչ մի պրոցես ֆայլը բաց չի պահում։ Hard link-երը չեն կարող անցնել ֆայլային համակարգերի միջև կամ ցույց տալ դիրեկտորիաներ։ Symbolic link-ը փոքրիկ ֆայլ է, որը պարունակում է ճանապարհ և կարող է ցույց տալ ցանկացած տեղ, նույնիսկ ոչ մի տեղ (dangling link)։ Այս մոդելը բացատրում է, թե ինչու է mv-ն մեկ ֆայլային համակարգի ներսում ակնթարթային (փոխվում է միայն դիրեկտորիայի գրառումը), իսկ ֆայլային համակարգերի միջև՝ պատճենում է տվյալները, ինչու բաց log-ի ջնջումը տեղ չի ազատում, և ինչու deploy գործիքները release-ները ատոմար փոխում են՝ փոխարինելով «current» symlink-ը։
ls -li /etc/hostname # inode number in the first column
stat /etc/hostname # full metadata, link count, times
ln data.csv data-copy.csv # hard link: same inode
ln -s /srv/app/releases/42 /srv/app/current.new
mv -T /srv/app/current.new /srv/app/current # atomic symlink swap
readlink -f /srv/app/current # resolve to the real path
find /srv -xtype l # dangling symlinks
10Script-եր, որոնք դիմանում են իրական տվյալներին
Production script-երը հանդիպում են բացատներով, նոր տողերով և գծիկով սկսվող ֆայլերի անունների, ընդհատված գործարկումների և մասնակի ձախողումների։ Երեք սովորություն լուծում են դրա մեծ մասը։ Առաջին՝ ֆայլերի ցուցակները փոխանցիր NUL-ով բաժանված՝ find -print0 xargs -0-ի հետ կամ while read -d '' ցիկլով, երբեք for ցիկլ ls-ի ելքի վրա։ Երկրորդ՝ ժամանակավոր ֆայլերը ստեղծիր mktemp-ով և ջնջիր trap-ում, որպեսզի մաքրումը կատարվի նույնիսկ script-ի ձախողման կամ ընդհատման դեպքում։ Երրորդ՝ իմացիր, թե որտեղ set -e-ն չի աշխատում՝ if պայմանների ներսում, && կամ ||-ի ձախ կողմում և local-ով վերագրվող command substitution-ներում, ուստի կարևոր քայլերը ստուգիր բացահայտ։ Օգտատիրոջ տված ճանապարհներից առաջ օգտագործիր -- օպցիաները փակելու համար, որպեսզի -rf անունով ֆայլը ընկալվի որպես անուն։ Կողպման համար flock-ը թույլ չի տալիս նույն job-ի երկու պատճենի միաժամանակ աշխատել։
#!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf -- "$tmp"' EXIT # always cleaned up
exec 9>/var/lock/report.lock
flock -n 9 || { echo "already running" >&2; exit 1; }
find /var/www/uploads -type f -name '*.jpg' -mtime -1 -print0 |
while IFS= read -r -d '' file; do
cp -- "$file" "$tmp/"
done
tar -czf /var/backups/uploads-today.tar.gz -C "$tmp" .
11Արագ տեքստի մշակում մեծ ֆայլերի վրա
Բազմագիգաբայթանոց log-ի վրա գործիքի և flag-երի ընտրությունը կարևոր է։ Locale-ը թաքնված ծախսն է․ UTF-8 locale-ով sort-ը և grep-ը կատարում են թանկ, նիշերը հաշվի առնող համեմատություններ, իսկ LC_ALL=C նախածանցը ստիպում է համեմատել բայթերը և հաճախ մի քանի անգամ ավելի արագ է, ինչը նորմալ է log-երի և IP-ների համար։ grep -F-ը pattern-ը դիտարկում է որպես ֆիքսված տող և շրջանցում է regex շարժիչը։ ripgrep-ը (rg) որոնում է զուգահեռ և սովորաբար ամենաարագն է կոդի ծառերի համար։ Սեղմված rotated log-երը կարդա տեղում՝ zgrep-ով, zcat-ով կամ zless-ով՝ առանց բացելու։ sort-ը -S-ով կարող է ավելի շատ հիշողություն օգտագործել, --parallel-ով՝ զուգահեռ թելեր, և ժամանակավոր ֆայլերը գրում է /tmp, եթե -T չես նշել, ինչը կարևոր է, երբ /tmp-ը փոքր է։ Վերջապես, pipeline-ում ամենաընտրողական ֆիլտրը դիր առաջինը, որպեսզի հաջորդ փուլերը ավելի քիչ տողեր մշակեն։
LC_ALL=C grep -F ' 502 ' access.log | wc -l
zgrep -h 'POST /login' /var/log/nginx/access.log.*.gz | wc -l
LC_ALL=C sort -S 1G --parallel=4 -T /var/tmp big.txt | uniq -c
rg -n --hidden -g '!node_modules' 'API_KEY' /srv/app
# Requests per minute around an incident
awk '{print substr($4, 2, 17)}' access.log | uniq -c | sort -rn | head
Իրական իրավիճակներ
Թրաֆիկի ցատկ․ ո՞վ է հարվածում կայքին
Խնդիր
Կայքը քսան րոպե դանդաղեց, իսկ hosting-ի dashboard-ը ցույց է տալիս միայն CPU-ի ցատկ։ Պետք է պարզես՝ իրական օգտատերե՞ր էին, crawler, թե՞ հարձակում, օգտագործելով միայն nginx-ի access log-ը։
Ինչպես լուծել
Աշխատիր կոպիտից դեպի մանրը։ Հաշվիր request-ները ըստ րոպեի՝ awk-ով timestamp դաշտի վրա, որպեսզի գտնես ճշգրիտ պատուհանը։ Հետո այդ պատուհանի ներսում դասավորիր client IP-ները awk '{print $1}' | sort | uniq -c | sort -rn | head-ով, և նույն ձևով՝ ճանապարհները 7-րդ դաշտով։ Ստուգիր ամենաակտիվ IP-ների user agent-ները (վերջին չակերտավոր դաշտը)․ հաճախ դա մեկ agent է, օրինակ՝ scraper կամ սխալ կարգավորված monitor։ Օգտագործիր LC_ALL=C և zgrep rotated ֆայլերի վրա, եթե պատուհանը անցնում է rotation-ի միջով։ Եթե դա չարաշահում է, կիրառիր rate limit կամ արգելափակիր nginx-ում կամ Cloudflare-ում, և one-liner-ները պահիր runbook-ում, որպեսզի հաջորդ ուսումնասիրությունը երկու րոպե տևի։
Մաքրման script-ը ջնջեց սխալ ֆայլերը
Խնդիր
Գիշերային script-ը cron-ից գործարկում էր rm -rf "$BUILD_DIR"/*։ Մի գիշեր փոփոխականը սահմանված չէր, քանի որ cron-ը չի բեռնում profile-ը, և script-ը ջնջեց ֆայլեր այն ֆայլային համակարգի արմատից, որտեղ աշխատում էր։
Ինչպես լուծել
Նախ վերականգնիր backup-ից, հետո script-ը դարձրու fail-safe։ Ավելացրու set -euo pipefail, որպեսզի չսահմանված փոփոխականը կանգնեցնի script-ը։ Վտանգավոր ճանապարհների վրա օգտագործիր ${VAR:?message} ձևը, որպեսզի expansion-ը ինքնին ընդհատվի դատարկ արժեքի դեպքում։ Վավերացրու թիրախը՝ ստուգիր, որ այն դիրեկտորիա է, գտնվում է սպասվող prefix-ի տակ, օրինակ՝ /srv/app/builds, և / չէ։ Glob-երի փոխարեն նախընտրիր find "$dir" -mindepth 1 -mtime +7 -delete և log արա, թե ինչ է ջնջվում։ Փոփոխականը սահմանիր script-ի կամ crontab-ի ներսում՝ միջավայրին ապավինելու փոխարեն, գործարկիր shellcheck և job-ը նորից միացնելուց առաջ փորձարկիր՝ rm-ի տեղում echo դնելով։
Ցիկլը կոտրվում է բացատներով ֆայլերի անունների վրա
Խնդիր
Upload արված նկարները փոխակերպող script-ը թեստում աշխատում է, բայց production-ում ձախողվում է «No such file or directory» սխալով այնպիսի անունների համար, ինչպես «Summer menu.jpg»-ն։
Ինչպես լուծել
Script-ն օգտագործում է for f in $(ls *.jpg), ուստի word splitting-ը յուրաքանչյուր անուն կտրում է բացատների վրա։ Փոխարինիր glob-ով՝ for f in ./*.jpg, որը յուրաքանչյուր անուն տալիս է որպես մեկ բառ, և ամեն օգտագործում վերցրու չակերտների մեջ՝ "$f"։ Ռեկուրսիվ աշխատանքի համար օգտագործիր find -print0, որը pipe է արվում while IFS= read -r -d '' f-ի մեջ, կամ find -exec ... {} +, որոնք անունները փոխանցում են անվտանգ, նույնիսկ եթե դրանք նոր տողեր են պարունակում։ Ֆայլային արգումենտներից առաջ ավելացրու --, որպեսզի գծիկով սկսվող անունները չընկալվեն որպես օպցիաներ։ Ավելացրու shopt -s nullglob, որպեսզի դատարկ դիրեկտորիան ոչ մի իտերացիա չտա՝ բառացի «*.jpg»-ի փոխարեն։ Փորձարկիր «թշնամական» անուններով լի fixture դիրեկտորիայով։
Արագ ուղեցույց
- Ինչ է իրականում գործարկվելու հրամանի համար
type -a node; command -v node- Debug անել script-ը տող առ տող
bash -x ./script.sh (կամ set -x ներսում)- Pipeline-ի յուրաքանչյուր փուլի exit status-ը
echo "${PIPESTATUS[@]}"- Ընդհատել, եթե փոփոխականը դատարկ է
rm -rf -- "${DIR:?DIR is not set}"/build- Անվտանգ ցիկլ գտնված ֆայլերի վրա
find . -type f -print0 | while IFS= read -r -d '' f; do ...; done- Հրաման գործարկել միանգամից շատ ֆայլերի վրա
find /srv -name '*.log' -exec gzip {} +- Որոնել սեղմված rotated log-երում
zgrep -h 'error' /var/log/nginx/error.log.*.gz- Արագ բայթային sort և grep
LC_ALL=C sort file | LC_ALL=C grep -F 'text'- Symlink-ի ատոմար փոխարկում
ln -sfn new_target link.tmp && mv -T link.tmp link- Shell script-ի lint
shellcheck deploy.sh
Իմ իրական նախագծերում
- Կառավարում եմ չորս production Ubuntu սերվեր key-based SSH-ով, և shell-ով եմ կարդում log-երն ու config-ը դրանց բոլորի վրա։
- Մեկ սերվերի սկավառակի զբաղվածությունը 91%-ից իջեցրի 66%-ի՝ find-ով և du-ով գտնելով ու ջնջելով հին log-երը, հին build-երը և չափազանց մեծ node_modules-ները։
- Production bug-երը, ինչպես localization middleware-ի սխալը և image-to-WebP փոխակերպման ձախողումը, գտնվեցին սերվերի վրա log-երը կարդալով՝ սկզբում միայն read-only, մինչև որևէ շտկում։
Հարցեր և պատասխաններ
Q1Ո՞րն է տարբերությունը Linux-ի և Ubuntu-ի նման դիստրիբուտիվի միջև։
Linux-ը kernel-ն է՝ միջուկը, որը կառավարում է hardware-ը, հիշողությունը, պրոցեսները և ֆայլային համակարգը։ Ինքնին այն օգտագործելի համակարգ չէ։ Դիստրիբուտիվը kernel-ը փաթեթավորում է package manager-ի, համակարգային գործիքների, լռելյայն կոնֆիգուրացիայի, փաստաթղթերի և աջակցության ցիկլի հետ։ Ubuntu-ն և Debian-ը օգտագործում են apt և .deb փաթեթներ, RHEL-ը, Rocky-ն և Alma-ն՝ dnf և .rpm։ Սերվերների համար գործնական տարբերությունները փաթեթների անուններն են, որոշ config ճանապարհներ, լռելյայն անվտանգության գործիքները (AppArmor Ubuntu-ում, SELinux RHEL-ում) և թե որքան է աջակցվում յուրաքանչյուր թողարկում։ Ես ընտրում եմ LTS թողարկում, որպեսզի անվտանգության թարմացումները տարիներով շարունակեն գալ՝ առանց պարտադիր upgrade-ների։
Q2Որտե՞ղ կփնտրես կոնֆիգուրացիան և log-երը Linux սերվերի վրա։
Կոնֆիգուրացիան /etc-ի տակ է՝ /etc/nginx, /etc/ssh/sshd_config, /etc/php, /etc/systemd/system՝ սեփական unit-ների համար։ Log-երը երկու տեղում են։ Դասական տեքստային log-երը /var/log-ում են, օրինակ՝ /var/log/nginx/error.log կամ /var/log/auth.log՝ մուտքերի համար։ systemd-ով կառավարվող ծառայությունները գրում են նաև journal-ում, որը կարդում ես journalctl -u servicename-ով։ Հավելվածի log-երը հաճախ հենց հավելվածի ներսում են, օրինակ՝ Laravel-ում storage/logs-ը։ Այնպես որ իմ հերթականությունն է՝ journalctl ծառայության համար, հետո /var/log վեբ սերվերի համար, հետո հավելվածի սեփական log դիրեկտորիան, և tail -f եմ օգտագործում, մինչ խնդիրը վերարտադրում եմ։
Q3Բացատրիր pipe-երը և վերաուղղորդումը օրինակով։
Յուրաքանչյուր պրոցես ունի երեք ստանդարտ հոսք՝ stdin (0), stdout (1) և stderr (2)։ Pipe-ը մի հրամանի stdout-ը միացնում է հաջորդի stdin-ին, և փոքր գործիքները կոմպոզիցիա են կազմում։ Օրինակ՝ awk '{print $1}' access.log | sort | uniq -c | sort -rn | head-ը տպում է ամենաշատ request-ներով տասը IP-ն․ awk-ը հանում է սյունը, sort-ը խմբավորում է նույն տողերը, uniq -c-ն հաշվում է դրանք, sort -rn-ը դասավորում է ըստ քանակի։ Վերաուղղորդումը հոսքը ուղարկում է ֆայլ․ >-ը վերագրում է, >>-ը ավելացնում է, 2>-ը հավաքում է միայն սխալները, իսկ 2>&1-ը սխալներն ուղարկում է այնտեղ, որտեղ գնում է stdout-ը։ Վերջինը կարևոր է cron job-երի համար, որտեղ երկուսն էլ ուզում ես մեկ log ֆայլում։
Q4Ինչպե՞ս կգտնես, թե որտեղ է օգտագործվում որոշակի ֆայլի անուն կամ կարգավորում ամբողջ նախագծում։
grep -rn «SETTING_NAME» /path/to/project-ը որոնում է ռեկուրսիվ և տպում ֆայլերի անունները տողերի համարներով։ Ավելացնում եմ -i՝ մեծատառ/փոքրատառը անտեսելու համար, --include='*.php'՝ ֆայլերի տիպերը սահմանափակելու համար, և --exclude-dir=node_modules՝ աղմուկը բաց թողնելու համար։ Մեծ codebase-երում ripgrep-ը (rg) նույնն անում է ավելի արագ և լռելյայն հաշվի է առնում .gitignore-ը։ Ֆայլերը ոչ թե բովանդակությամբ, այլ անունով գտնելու համար օգտագործում եմ find /path -name 'pattern', որը կարող է նաև զտել ըստ չափի, տիպի և տարիքի, օրինակ՝ find /var/www -name '*.log' -size +100M՝ մեծ log-երը գտնելու համար։ find-ը -exec-ի կամ xargs-ի հետ համատեղելով կարող ես գործողություն կատարել արդյունքների վրա, բայց ես միշտ նախ ցուցակն եմ տպում։
Q5Ինչո՞ւ bash script-ը պետք է սկսվի set -euo pipefail-ով։
Լռելյայն bash-ը շարունակում է աշխատել հրամանի ձախողումից հետո, չսահմանված փոփոխականները դատարկ տող է համարում և pipeline-ի exit status-ի համար նայում է միայն վերջին հրամանին։ Սրանցից յուրաքանչյուրը թաքցնում է սխալներ։ set -e-ն կանգնեցնում է script-ը, երբ հրամանը ձախողվում է։ set -u-ն փոփոխականի անվան տառասխալը սխալ է դարձնում դատարկ արժեքի փոխարեն, ինչը կանխում է այնպիսի աղետներ, ինչպես rm -rf «$DIR/»-ի վերածվելը rm -rf /-ի։ set -o pipefail-ը pipeline-ը ձախողված է դարձնում, եթե որևէ փուլ ձախողվի։ Սա կատարյալ չէ, քանի որ որոշ կառուցվածքներ շրջանցում են -e-ն, ուստի ես միևնույն է փոփոխականները չակերտների մեջ եմ վերցնում, կարևոր արդյունքները բացահայտ ստուգում եմ և գործարկում եմ shellcheck։
Սենիոր մակարդակի հարցեր
S1Ինչո՞ւ է մեծ log ֆայլի ջնջումը երբեմն ոչ մի տեղ չազատում, և ինչպե՞ս ես դա լուծում։
rm-ով ջնջումը հեռացնում է միայն դիրեկտորիայի գրառումը։ Inode-ը և դրա բլոկներն ազատվում են, երբ link-երի քանակը հասնում է զրոյի, և ոչ մի պրոցես ֆայլը բաց չի պահում։ Վեբ սերվերը կամ հավելվածը, որը բացել էր log-ը, շարունակում է գրել արդեն անանուն inode-ի մեջ, ուստի df-ը դեռ ցույց է տալիս տեղը զբաղված, իսկ du-ն չի կարողանում գտնել այն․ սա df/du անհամապատասխանության տիպիկ դեպք է։ lsof +L1-ը ցուցակում է նման ֆայլերը իրենց PID-երով։ Լուծումներն են՝ ստիպել պրոցեսին նորից բացել իր log-երը (nginx -s reopen, reload կամ restart) կամ truncate անել բաց ֆայլը /proc/PID/fd/N-ի միջոցով։ Կանխարգելումն ավելի լավ է․ կենդանի log-երը truncate արա՝ ջնջելու փոխարեն, և օգտագործիր logrotate՝ postrotate reopen-ով կամ copytruncate-ով, ընդունելով, որ copytruncate-ը կարող է կորցնել պատճենման ընթացքում գրված մի քանի տող։
S2Ե՞րբ է bash script-ը դադարում լինել ճիշտ գործիք։
Bash-ը հիանալի է այլ ծրագրերի կառավարման համար՝ գործարկիր սա, ստուգիր exit code-ը, տեղափոխիր այս ֆայլերը։ Այն դառնում է ռիսկային, երբ խնդրին պետք են տվյալների կառուցվածքներ, JSON-ի կամ այլ կառուցվածքային ֆորմատների parsing, ոչ տրիվիալ սխալների մշակում, backoff-ով retry-ներ, concurrency կամ unit test-եր։ Չակերտների և splitting-ի կանոնները հեշտացնում են նուրբ bug-երը, իսկ set -e-ն ունի հայտնի բացեր։ Իմ գործնական կանոնն է՝ երբ script-ը անցնում է մոտավորապես մեկ էջը, աշխատում է կառուցվածքային տվյալների հետ կամ մի քանի մարդ է այն սպասարկելու, տեղափոխում եմ Python կամ Node, կամ Ansible-ի նման կոնֆիգուրացիոն գործիք, եթե այն նկարագրում է սերվերի վիճակը։ Գինը լրացուցիչ runtime կախվածությունն է, մինչդեռ bash-ը կա ամենուր, ներառյալ մինիմալ container-ները, որոնցում կարող է լինել միայն POSIX sh։
S3Ինչպե՞ս կդարձնես release-ների միջև deploy-ի փոխարկումը ատոմար։
Յուրաքանչյուր release build արա իր սեփական դիրեկտորիայում, օրինակ՝ releases/2026-11-05-1200, այնտեղ տեղադրիր կախվածությունները և գործարկիր ստուգումները, մինչ հին release-ը շարունակում է սպասարկել։ Հետո մեկ symlink՝ current, փոխարկիր նոր դիրեկտորիայի վրա։ Ուղղակի ln -sfn-ով դա անելը ատոմար չէ, քանի որ այն ջնջում և նորից ստեղծում է link-ը՝ թողնելով փոքրիկ բաց։ Ժամանակավոր link ստեղծելը և այն mv -T-ով հինի վրա վերանվանելը ատոմար է, քանի որ rename-ը ֆայլային համակարգի մեկ գործողություն է։ Վեբ սերվերը կամ process manager-ը ցույց է տալիս current-ը, ուստի reload-ը վերցնում է նոր կոդը։ Rollback-ը նույն փոխարկումն է՝ հետ դեպի նախորդ դիրեկտորիա։ Զգույշ եղիր cache-երի հետ, որոնք պահում են լուծված իրական ճանապարհները, ինչպես PHP-ի OPcache-ը և realpath cache-ը, որոնց համար փոխարկումից հետո կարող է PHP-FPM reload պետք լինել։
Բառարան
- Inode
- Գրառում, որը պահում է ֆայլի տվյալների տեղը և մետատվյալները։ Ֆայլերի անունները պարզապես դիրեկտորիայի գրառումներ են, որոնք ցույց են տալիս inode համարը։
- Hard link
- Նույն inode-ի լրացուցիչ անուն։ Տվյալները մնում են, քանի դեռ բոլոր անունները չեն ջնջվել, և ոչ մի պրոցես այն բաց չի պահում։
- Symbolic link
- Փոքր ֆայլ, որը պարունակում է այլ ֆայլի կամ դիրեկտորիայի ճանապարհ։ Կարող է անցնել ֆայլային համակարգերի միջև և կարող է «կախված» մնալ։
- fork / exec
- Ինչպես է Unix-ը գործարկում ծրագրերը․ պրոցեսը պատճենում է իրեն (fork), հետո պատճենը իրեն փոխարինում է նոր ծրագրով (exec)։
- Builtin
- Հրաման, որն իրականացված է հենց shell-ի ներսում, օրինակ՝ cd-ն կամ export-ը, քանի որ այն պետք է փոխի shell-ի սեփական վիճակը։
- Word splitting
- Bash-ի կողմից առանց չակերտի expansion-ի բաժանումը առանձին արգումենտների բացատների, tab-երի և նոր տողերի վրա։ Չակերտները կանխում են դա։
- Globbing
- *.log-ի նման pattern-ների ընդլայնումը համապատասխան ֆայլերի անունների, որը shell-ը կատարում է մինչև հրամանի գործարկումը։
- File descriptor
- Փոքր թիվ, որով պրոցեսը հղվում է բաց ֆայլին, pipe-ին կամ socket-ին։ 0-ն, 1-ը և 2-ը ստանդարտ հոսքերն են։
- Locale
- Լեզվի և նիշերի կարգավորումներ (LANG, LC_ALL), որոնք փոխում են, թե ինչպես են գործիքները դասավորում, համեմատում և ցուցադրում տեքստը։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։