1. İş qovluğu və CWD
CLI yazanda siz istəmədən fayl sistemində bir az “turist bələdçisi”nə çevrilirsiniz: istifadəçi yol verir, siz isə onun hara getmək istədiyini anlamağa çalışırsınız. Burada əsas anlayış üzə çıxır: prosesin cari iş qovluğu var (Current Working Directory, CWD). Bütün nisbi yollar məhz bundan asılıdır.
Təsəvvür edin ki, CLI əlində fənər olan bir insandır. Fənər “burada və indi”ni işıqlandırır. Nisbi yol “irəli 10 addım get” deməkdir, mütləq yol isə “GPS koordinatlarına teleport ol” kimidir. Əgər insanın harada dayandığını bilmirsinizsə, “irəli 10 addım” lotereyaya çevrilir.
Swift-də CWD-ni almaq çox asandır — FileManager vasitəsilə.
Nümunə: CWD-ni sətir kimi oxuyuruq
import Foundation
let cwd = FileManager.default.currentDirectoryPath
print("CWD = \(cwd)") // məsələn: CWD = /Users/alex/Projects/LibraryCLI
Vacibdir: CWD — bu, mənbə faylları olan qovluq deyil və “Package.swift haradadır” demək də deyil. Bu, məhz “hazırda terminal/proses haradadır” deməkdir. swift run ... ilə işə salmaq CLI-ni başlatmağın yayğın yollarından biridir, amma CWD yenə də proses mühitindən götürülür.
2. Nisbi və mütləq yollar
Başlanğıc proqramçı yolları görəndə çox vaxt belə düşünür: “Mütləq yol — / ilə başlayan yoldur”. Unix sistemlərində (macOS/Linux) bu, adətən doğrudur, amma daha düzgün yanaşma simvollara yox, mənaya baxmaqdır: mütləq yol CWD-dən asılı deyil, nisbi yol isə asılıdır.
Gəlin bunu kiçik bir cədvəldə sabitləyək. Burada heç bir sehr yoxdur, sadəcə sağlam düşüncə var.
| İstifadəçi girişi | Mənaya görə tip | Nəticənin nədən asılı olduğu |
|---|---|---|
|
mütləq | heç nədən (bir fayl sistemi daxilində) |
|
nisbi | prosesin CWD-sindən |
|
nisbi | prosesin CWD-sindən |
|
nisbi | prosesin CWD-sindən |
Bu, məhz CLI üçün niyə vacibdir? Çünki CLI tez-tez “düzgün qovluqdan yox” işə salınır. Bu gün /Users/me/Projects-dəsiniz, sabah — /tmp-də, birigün isə CI sizi ümumiyyətlə hansısa xidmət qovluğunda işə salır. Əgər nisbi yolların şərhini nəzarətdə saxlamırsınızsa, tətbiq “bəzən işləyəcək”, bu isə bildiyiniz kimi, işləməməyin ən pis növüdür.
3. Fayl yolları üçün URL
Kursun bu günündə URL-ə “sayta keçid” kimi yox, fayl sistemində ünvan üçün tip kimi baxırıq (file URL). Bu, snobluq deyil, səhvlərdən qoruyan praktik yanaşmadır: URL yolları düzgün birləşdirə, komponentlər və genişlənmələr əlavə edə, həmçinin “bu qovluqdur, yoxsa fayl” məlumatını saxlaya bilir.
FileManager.default.currentDirectoryPath sətir qaytarır, amma fayl yolları ilə sonrakı iş URL-də daha rahatdır, çünki:
- URL “yol” ilə “mətn təsviri”ni açıq şəkildə ayırır
- URL əl ilə birləşdirmə aparmadan yolu qurmaq üçün təhlükəsiz əməliyyatlar verir
- URL çox vaxt məhz URL və ya path qəbul edən FileManager ilə təbii şəkildə uyğunlaşır
Nümunə: CWD file URL kimi
import Foundation
let cwdPath = FileManager.default.currentDirectoryPath
let cwdURL = URL(fileURLWithPath: cwdPath, isDirectory: true)
print(cwdURL.path) // məsələn: /Users/alex/Projects/LibraryCLI
print(cwdURL.absoluteString) // məsələn: file:///Users/alex/Projects/LibraryCLI/
Fərqə diqqət yetirin:
- path — fayl sistemi üçün “normal yol”dur (FileManager-ə adətən gedən məhz budur).
- absoluteString — URL sətiridir, çox vaxt file:// ilə. Gözlə baxmaq rahatdır, amma bu, adətən yola gözləyən API-lərə ötürmək istədiyiniz şey deyil.
Əgər nə vaxtsa “mən yol verdim, amma niyə file:///... deyir” hissi keçirmisinizsə — təbrik edirəm, siz absoluteString ilə tanış olmusunuz. Narahatdır, amma öyrədicidir.
4. Yolun həlli: sətir → mütləq URL
İndi əsas məqama gəlirik. Məqsədimiz: istifadəçinin verdiyi sətiri (məsələn data/library.json və ya /var/lib/app/data) götürüb, sonradan FileManager-də işləyə biləcəyimiz mütləq file URL-ə çevirməkdir.
Burada minlərlə proqramın taleyini pozan bir cazibə var: belə etmək istəyirsən split(separator: "/"), sonra “düzgün slash”larla birləşdirmək, sonra ..-ni emal etmək, sonra birdən Windows-u xatırlamaq… və sonda fayl sisteminin mini versiyasını yazmaq. Spoiler: uduzacaqsınız. Həmişə.
Bunun əvəzinə URL(fileURLWithPath:relativeTo:)-dən istifadə edirik.
Nümunə: “yolu həll et” funksiyası (resolve)
import Foundation
func resolvePath(_ input: String, relativeTo baseDir: URL) -> URL {
// Mütləq yol üçün baseDir nəzərə alınmayacaq.
// Nisbi yol üçün isə input baseDir-ə nisbətən şərh ediləcək.
URL(fileURLWithPath: input, relativeTo: baseDir)
}
let base = URL(fileURLWithPath: "/tmp", isDirectory: true)
let a = resolvePath("LibraryCLI/data", relativeTo: base)
let b = resolvePath("/var/log", relativeTo: base)
print(a.path) // /tmp/LibraryCLI/data
print(b.path) // /var/log
Burada məntiqi anlamaq vacibdir: bizə əvvəlcədən “mütləqdir, yoxsa nisbi”ni müəyyən etmək lazım deyil. Biz if input.hasPrefix("/") yazmırıq. Biz bu işi məhz bunun üçün mövcud olan sistem API-yə veririk.
Belə kodun “davamlılığı”nın demək olar ki, hamısı ondadır ki, o qısadır. Kod nə qədər azdırsa, səhv etmək üçün yer də bir o qədər az olur.
Bunu CLI-də harada istifadə etmək olar
Canlı CLI-də yollar adətən iki yerdə yaranır: ya istifadəçi yolu açıq verib (məsələn, “bazanı bura saxla”), ya da siz standart yolu seçmisiniz (“tətbiqin məlumatlarını bizim qovluqda saxlayırıq”). “Defolt olaraq harada saxlanılır” siyasətini günün növbəti mühazirəsində araşdıracağıq, amma indi mexanikaya fokuslanırıq: istifadəçi yol sətri veribsə, onu düzgün şəkildə URL-ə çevirməliyik.
Kod baxımından bu belə görünür: bizim arqumentlərdən String? var (çünki istifadəçi onu verməyə də bilər) və baseDir var, bu da çox vaxt CWD olur.
Nümunə: “istifadəçi yolu və ya CWD+defolt”
import Foundation
let cwdURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath, isDirectory: true)
let userProvidedPath: String? = nil // təsəvvür edək: arqument verilməyib
let fallback = "LibraryCLI-data" // nümunə üçün müvəqqəti defolt
let chosenPath = userProvidedPath ?? fallback
let resolved = URL(fileURLWithPath: chosenPath, relativeTo: cwdURL)
print(resolved.path) // məsələn: /Users/alex/Projects/LibraryCLI/LibraryCLI-data
Diqqət edin: bu mühazirədə hələ qovluq yaratmırıq və icazələri yoxlamırıq. Biz yalnız ən vacib hazırlıq addımını atırıq: girişi “normal koordinata” çeviririk — file URL-ə.
5. Nisbi yolların incəlikləri: ., .., ~
Nisbi yollar . və .. elementlərini əhatə edə bilər. Əgər siz bunları bir dəfə də əllə emal etməyə çalışmısınızsa, bilirsiniz ki, bu ya bug ilə, ya da “qoy çaxsın” kimi fəlsəfi qəbul ilə bitir. Biz isə elə etməyi öyrənirik ki, çaxmasın (və ya ən azı mənalı şəkildə çaxsın).
URL(fileURLWithPath:relativeTo:) artıq bazaya nisbətən düzgün yolu, o cümlədən ..-ni yığmağı bacarır. Gəlin bunu praktikada görək.
Nümunə: .. ilə nisbi yol
import Foundation
let base = URL(fileURLWithPath: "/tmp/app/work", isDirectory: true)
let url = URL(fileURLWithPath: "../data/library.json", relativeTo: base)
print(url.path) // /tmp/app/data/library.json
İdeya sadədir: siz ..-ni “anlamağa” çalışmırsınız, sadəcə niyyətinizi təsvir edirsiniz: “bax baza, bax giriş”. Qalanını sistem sizin split-inizdən daha yaxşı bildiyi işi görür.
Günün planından ayrıca vacib qeyd: bu mühazirə çərçivəsində biz shell sehri kimi ~ (ev qovluğu) və yol sətrindəki mühit dəyişənləri üçün dəstək vermirik. Əgər terminalda ~/file.txt yazırsınızsa, shell çox vaxt bunu tətbiqiniz arqumentləri görməzdən əvvəl mütləq yola genişləndirir, amma bunu müqavilə kimi qəbul etmək düzgün fikir deyil. Kursumuzda belə qaydanı ya açıq şəkildə sonra əlavə edəcəyik, ya da ümumiyyətlə vəd etməyəcəyik.
6. Diaqnostika və data axınının sxemi
CLI-ni dayanıqlı etdikdə “konveyer”i yadda saxlamaq çox kömək edir: nə aldıq, necə çevirdik və hansı addımda xəta baş verə bilər. İndi konveyerimiz hələ qısadır, amma artıq düşüncə tərzini intizamlandırır.
Mini sxem: sətirdən file-URL-ə
Aşağıdakı sxemi hər dəfə path.split(separator: "/") etmək istəyəndə xatırlamaq lazımdır (bu, “gecə saat 2-də peçenye yemək istəmək” kimidir: qadağan deyil, amma nəticələr qəribə olur).
flowchart TD
A["İstifadəçidən gələn sətir (String)"] --> B["CWD (String)"]
B --> C["baseDir: URL(fileURLWithPath:isDirectory:)"]
A --> D["URL(fileURLWithPath:relativeTo:)"]
C --> D
D --> E["resolvedURL.path -> FileManager əməliyyatları"]
Məsələ ondadır ki, resolvedURL sonrakı həyat üçün rahat olan vahid formadır: qovluqlar yaratmaq, mövcudluğu yoxlamaq, faylları oxumaq/yazmaq (bunlar kursun növbəti günlərində olacaq).
Niyə yol “səhv yerə” düşdü, bunu necə başa düşmək olar
CLI-də xüsusi bir ağrı növü var: “istifadəçi düzgün yol verdiyinə and içir, amma siz hər şeyin başqa qovluğa uçduğunu görürsünüz”. Çox vaxt səbəb birdir: insanın güman etdiyi CWD deyil.
Ona görə də yolu “həll etmək” mərhələsində diaqnostik loq saxlamaq faydalıdır: baza nə idi, giriş nə idi, nəticə nə oldu. Və bəli — həmin kontekst #file / #line / #function də çox faydalıdır, xüsusən də yolları bir neçə yerdə yığanda.
Nümunə: resolve-u loqlayırıq (mövcud Logger-dən istifadə edirik)
import Foundation
func resolvePathForCLI(_ input: String, baseDir: URL, logger: any Logger) -> URL {
let result = URL(fileURLWithPath: input, relativeTo: baseDir)
logger.debug("resolve input=\(input) base=\(baseDir.path) -> \(result.path)")
return result
}
Biz yeni Logger yaratmırıq və onun müqaviləsini dəyişmirik. Sadəcə hansı sahələrin həqiqətən kömək etdiyini göstəririk: input, baseDir.path, result.path. Sonradan həm vaxtı, həm də sinir hüceyrələrini xilas edən minimum budur.
7. CLI-də iş qovluğu və yollarla işləyərkən tipik səhvlər
Səhv №1: nisbi yolun “layihə qovluğundan” hesablandığını düşünmək.
Bu, çox yayğın tələdir: insan swift run ...-ı bir qovluqda işə salır, sonra başqa qovluqda, sonra IDE üçüncü qovluqdan işə salır və birdən “dünən işləyirdi” deyir. Əslində nisbi yol prosesin CWD-sindən hesablanır. Əgər sizə proqnozlaşdırıla bilən kök lazımdırsa, base-qovluq siyasətini açıq şəkildə seçməli və girişi açıq şəkildə URL-ə çevirməlisiniz.
Səhv №2: yolun mütləqliyini əl ilə yoxlamalar və budaqlanma ilə müəyyən etməyə çalışmaq.
if input.hasPrefix("/") { ... } else { ... } tipli kod yaranır. O, macOS-da “işləyir” də, buna görə kostyumlu fırıldaqçı kimi inandırıcı görünür. Amma bu, .., artıq ayırıcılar və ümumiyyətlə müxtəlif platformalar kimi nüanslarda sınır. Daha sadə və etibarlı yol: həmişə URL(fileURLWithPath:relativeTo:)-dən istifadə edin və sistem API-yə etibar edin.
Səhv №3: yolları split(separator: "/") ilə parçalamaq və sonra yenidən yığmaq.
Əvvəlcə bunun sürətli olduğu görünür. Sonra məlum olur ki, ikiqat slashlar var, . və .. var, genişlənmələr var, “qovluq vs fayl” var, bir də təsadüfən artıq bir / əlavə edib qəribə yol alırsınız. Nəticədə siz həqiqi URL-dən daha pis olan öz “mini-URL”inizi yazırsınız. Ən yaxşı həll — ümumiyyətlə buna başlamamaq və yolları URL və appendingPathComponent ilə yığmaqdır.
Səhv №4: yolun düzgün olmayan təqdimatını çap etmək və loqlamaq (absoluteString əvəzinə path).
absoluteString “hə, bu da sətirdir də, niyə ilişirsiniz” kimi görünür, amma fayl əməliyyatları üçün sizə demək olar ki, həmişə path lazımdır. Loqlarda file:///tmp/... görürsünüzsə, amma bunu FileManager-ə yol kimi verirsinizsə, “belə fayl yoxdur” xətası alınacaq və siz ekrana uzun müddət “amma o buradadır axı!” baxışı ilə baxacaqsınız. Ayırın: fayl sistemi üçün — path, “gözlə baxmaq” üçün — absoluteString istifadə oluna bilər.
Səhv №5: problemlər zamanı CWD-ni loqlamamaq.
Nəsə səhv gedəndə “faylı açmaq alınmadı” loqunu yazmaq istəyirsiniz. Amma CWD olmadan bu cümlə demək olar ki, faydasızdır: proqramın harada “axtardığını” başa düşmürsünüz. Yol problemlərinin diaqnostikasında CWD — naviqator koordinatları kimidir: onsuz “kim səhv döndü” mövzusunda sonsuz mübahisə edə bilərsiniz.
GO TO FULL VERSION