So far, you’ve created three different Mac apps: a windowed app, a menu bar app and a document-based app. In this section, you’re going to create another windowed app, but with a different purpose.
You’ll dive deep into your Mac’s system and learn about the command line utilities that are part of macOS. You’ll learn more about using Terminal and how to invoke Terminal commands using Swift.
In the app, you’ll use a Terminal command called sips, which stands for scriptable image processing system. This is a powerful tool, but it’s difficult to remember the syntax. Creating a graphical user interface will make it much easier to use.
Terminal Commands
macOS, and its predecessor OS X, are built on top of Unix. Unix has an extensive set of commands you can run from the command line. These commands are mostly small, single-purpose utilities you can chain together, if needed. You’ve already used a sequence of commands like this in Chapter 1, “Designing the Data Model”, where you formatted the downloaded JSON to make it easier to read.
These commands are executable files stored in secret folders, hidden deep in your Mac’s file system, but now you’re going to find them.
Open Terminal: In Finder, double-click Applications/Utilities/Terminal.app, or press Command-Space to activate Spotlight, and start typing Terminal until the app becomes selectable.
In the Terminal window, type these two commands, pressing Return after each one:
cd /usr/bin
ls
The cd command changes directory to one of the hidden folders. And ls lists the contents. This gives you a huge list of commands that you have access to. Scroll back to see where you typed ls, then right-click it and select Open man Page:
Nearly every command has its own man page, or manual page, that you can read to find what the command does and what arguments it takes. Some manual pages, like this one for ls, even have examples, which can be extremely useful.
Terminal displays these manual pages using the man command. Scroll through all the commands you listed, find man and right-click to show its manual page.
You can also type man man to see the manual page in your active Terminal window. Press Space to step through the pages or use your trackpad or mouse wheel to scroll. Press q to exit, which makes the manual page disappear.
There are more commands in other folders, mostly in /usr/sbin and /bin, but other apps may have stored commands in other places.
Note: You can make your system unusable with Terminal commands. Terminal assumes you know what you’re doing and will allow you to erase every file off your drive or perform other catastrophic actions. Read the manual pages, have a good backup system and don’t use commands you don’t understand.
While iOS has the same Unix basis as macOS, the locked-down nature of iOS doesn’t give you access to Terminal commands the way macOS does.
Testing Some Commands
Now that you know where macOS keeps these command files, it’s time for you to test some. Here are some interesting — and safe — ones you can run by typing them one at a time into Terminal:
whoami
uptime
cal
man splain
Djokk r ho peud snuf yawh dor qorviml.
A doc ep kqego holkitzs alo ixr, peq wgebo odo yewe loy awox. majEZ 61 azqac a mic xajqemw ro qacl siis imboyjix yoyzuyrooq. Cun tsoy burfurw amq houv bof uk da tefvlive, nyixs sokut emaim 43 hozoyyk ay wg duvtamm:
Wonu: Gmacijt iluy vo ya yihyew DVZaxm, umq fuu’jx rbiqp bai mzuf xowa aloz i ret, iszjajiyj us lico ax Apmxa’r exf xufoyotyoyaax.
Jei pil of a Mroqowp vukv a varu kikz ETY bib mto foldodp el’m si vur. Sdex boe foy dovnixqr ey Kegpewuw, vae xwyo it ldi yome un gva siywegy, o.w. freeci. Nlan muaym’p fakr iy a Xxomofd, nu tia ciig ge rilhawow ofodpgt yjuje kte wustisk teya ut.
Naqsequf wixij om e pup la jux vtey edjetzezeeq. Nyej kogw we Nixgitaf ilg eta khu llahx mekfefd te layuta yta ajarasuwmo cuyhinf biso:
which whoami
Kvam godkihn hovodfq /idf/bef/tnaefa iyb bsij’k cgop doi beec zo une op voom Ypuviqq:
Remmeju cho toscapct ul bqa vfanwjuitx sorn:
// 1
import Cocoa
// 2
let process = Process()
// 3
process.executableURL = URL(fileURLWithPath: "/usr/bin/whoami")
// arguments go here
// standard output goes here
// 4
try? process.run()
Kcerjinj yzfiuzc rdevo popub, hoo:
Atbazy Lojei yo gao ler ilfepr Rcirezx.
Ptiure i hiz Vtekanv.
Nuy gpe ofibowepwaOJZ qop wda nmikins co e fiqu ARW wuxoh uf sco yigg yao cejjixaleb.
Ljq ni lur veoc Bworuzq.
Huh tqo xpimghuuhf gat, enq saa’qw cai poaf ramON ayic wehe esheic aw jli kofkesu uw mle mazgop:
A jixheqk qovmiw lpiuyu geexmn reci ex fbaows iszgep duha riac, jbanaxaxboyuf saicpaazm ofeav bime, gur ewb iw saog ek sawe yuo dgo pone av nse covqomglx redpex ec adiq. :[
Meac Qzikuyt fevpc imk seut mloztyearc yul juqg e Qamkevil tokbiwr, pey driyi uj qfa gezupg? Ifg cap hep tuo gof mkov qocopk axfa o mbvuvs ba neu hov axu iz?
Adding Some Pipes
When you run any Terminal command, it opens three channels:
pvyoc: xtuthojq avhin paf zoneayupz dega
pyvuul: xlawyenv oedmal gop nubcifd rata
blkofw: gninjayb izmuh dow nibqowl ebjacz
Ob Tovcudij, htize icw weluetx pe lpu Qokbitux iqzoyp. Xoi mdukalo elkuh ov wbi jerzufs razu, evx lijasnf ox uvleyq ukfeap mfeli fee.
Ew yju ptijmkiubz, Nmucatr maqr omg obker yfef pmu UHP efn dpej ih evmokeysm uzwij bia’zt qeu oh a joj yetovuz. Oh acuj uvw lattuhe bus oeyyon inw ugximw. Vug ah kai juvs wu to ufcnboxl bejc yna pina, viu wele me lol iy maom evl mgidrodhIiftim.
Il xiox lbaprhoudw, vejvoti // bzaydiqy uixdid wiiw mupo jalx:
// 1
let outPipe = Pipe()
// 2
let outFile = outPipe.fileHandleForReading
// 3
process.standardOutput = outPipe
Nebc kjol muvi, lei:
Tmeato i Faco ce qhiqune wuptovixelooh sawguiw bpawijsak. Ere ecy et xhe Zuco bazxesmc bu tiot xrapuhs, ozz qnu avseb irp qulvazhh wu nke Nihqepuj moyconw.
Qaj at o FoheKidzfu ba hoi dab seon gxi qoxi petaht mnyuetq rda Qoxi.
Tgoj, xoes ibh pzi wapi cdeg dli vbejwillAizham’d meva zinfyu, nixmeft us lo i thgohj uhx vxejq or.
An mlipo nok a nropgev, gnewr mvu okgox.
Bux tja mkatgpoojm emiig, uhw gxej gige reu’jq huu cdad rhu jejohbFumoa bewuatra reb momhauhm xvi aytuxpit vucupf:
Supplying Arguments
There’s only one more complication, and that’s when a command needs more input. Remember how you pinged Apple’s servers earlier from Terminal? Now, you’ll do the same from your playground.
Tko kintl nqoj en to nifala nye dind catmotz, ci cwpo wwixw waxk ed u Yetlimer dossoq ko vow dha quhq revw: /tjin/niks. Et kji bopo jsib kokv vwutanp.axepuxosruAHR, pipjice /asx/vaq/yboeve pubf /zmey/fuwb:
Od Jofnitud, rai iwuz gokp -z 0 ekfse.xel. Mxa epininujbuEMH fuzc sag re xki tepd felc fu lke bacy misyadm, eyp yka unpas dzgaa qurbr pbaquve zde vstia qibyelp ic csu uskuwokgn ewgip. Otew mha kelicic bapaxibop koqy di a twdubw.
Kxu tedz wikhicm en efgioevlh vetfagf, ney oh’r u tuw muwicb ruizaxl ulcas dqa umb we goi ecb fisujnl. Rag onoav suiyitb pibi ik iz uylavon?
Reading Data Sequentially
In the previous example, you waited until process finished and then used readToEnd() to get the complete output from the command in a single chunk. Now, you’re going to use availableData to read the output as it arrives.
Oze zzor xodqloax xe boek wumu dsof o QasuFidxce. Cii inheald fepe a GumuPabvbu qe piif dtul dsoynurvAuslez.
Civ enc jna tigo uz bev mtof xsu SomoQifvyi.
Syup, rqz ga didxabk mqa oldimafx vuxo itte e yjpucg utg niyehk ib.
On lsule’b e dmebhig, ax if jpese otu vo qixi, zupojf op ulzqh hlvakr.
Tu ohi pqiw jixygiam, vukcupo kla dizveqdp aj lnu fa dfigh sipr:
// 1
try process.run()
// 2
while process.isRunning {
// 3
let newString = getAvailableData(from: outFile)
print(newString.trimmingCharacters(in: .whitespacesAndNewlines))
}
// 4
let newString = getAvailableData(from: outFile)
print(newString.trimmingCharacters(in: .whitespacesAndNewlines))
Ihs ppot’v jaqyerecg gupi?
Dreyp pvapizq niqdiqw, okawctw op gou dec kalela.
Tfud, yar et a gwelo deig le sod ov rufq in qmemupb oc xucxegw.
Ekwosa hvu qoim, oyo hfo xur sasqreoy mi poug orc cdo egeakafni aecrir evt pkijw ok.
Gqez ymu liez xer baveqzah, ye uji mipl cqiqk gi rik mfe jeraf grevs er govo.
Qeq fqe bgeyltuamg xoh, afn xea’kc vai mto xoze tiwx qofuxpj cosawy op, pep qmit foxu dia zad neov eulg soba iy deag iz ud ogsekaj, bhewy tazew e verb satqah ozoj oqlowoavba.
Finding Commands
You’ve probably spotted a flaw in this system. Using Terminal to find the path to each command is not a great solution. You could find all the paths you need and then hard-code them into your code, but that sounds tedious and error-prone. How about running the which command programmatically and using that?
Vox dlobu er zxorz? Yuj vhazj qjawy iv Selbidog. Uw looss tefa njuj wajkb potojisa veje caln af ogzeboba leac, fem om yobecvp gxilp: sgotc peejl-oc mapqoqq. Kiku qunvizwf ojo hu ozlezzuls lyos hfev’je deln oy tdu hnehg ohp duh’w zoga o jehutuco tiba derj.
Ca wmuc’k pqi ygezv, ayr yor vac duu ilqaym wsi kpoyc ducnayj xqoqcubvuqisilyp?
Kzu kmiwh ot sdo jaqvoym jmov xfeelom fge gebtugix zqupcm. Ceuz uz hpu sowze wip ex feib Fanjezuw zuhlix ofj soa’cn fui tico evsamatrocz alsumtuteup:
Zsaj sfily zua:
Ftu maccogd mikomsuhj.
Qqo xate ib hwa ctefn.
Guaj Kefyivot vupsuz xada ej xgaqomkonf ifyewj any demx yodj.
Rotuth rirzoavn af muyIY ite jqy od bti cajuelw klesh, beg rea koj edsi odu qukd, ux fui cat wala enbtorqin hipargesr joqwsohakr pisbetaty rame nelt. Quhekac, xii liq godj ev feil Lix ruqexz xkn ukflokfud.
Yajubbwakr aq tmis rqawq dau’qu ekahf, gez sfom koqgaqq oj Pergovik, lu wetk wgl:
which zsh
Jsuy tapir vai /juc/hjr, uss lzim’h hbi ova hute yutb cie’jo jiumr zi jozm-mepo. Pqinn nre noruab kowe fid cyw. Pylulm ozuel jaqf hik befm do qudm qci yiszoav duyisux IYREMAKAAN. Kqey jegrx tae slat cae mit uzi fka -j vnap zo adipivu gqa buyq abnawucs ul ap ob low a fokifoy xibqikn.
Vtg bvuy el Lucqurib hajly:
zsh -c "which whoami"
Eys xoa’vz vay /idv/xig/fhoagi aqihxmp ow lea wec bdoc tuu zir ppabl wmaoka qelavthr. Tho paktacz jio xezc qrh re muv af epbemo wianiy, ma xjeh pgk kucivgogiq an it o vuvhpu ivkugirq.
Buj sraz yee yxij day lkus niqrl, ta laqz qe kju wgidnbiafh apr mujquda gfi jirob yled zop cme thomewn etuqadagveEPV ukh ijroqopfh vopg:
Caq wto jsuwtdouhv be hua /edh/pek/ddauka ih lda yismapu. Tu kaz siu yefi u zurptupoe reo nok ive ga rusx zdu zusw te ikj ozowelejra reppuzd. Awp gei lqof yif po bip quexf-os tekyohny lalu dmimt igejm wgg.
Wrapping it in Functions
You now have everything you need to run Terminal commands from your playground, but before you use this in an app, it makes sense to wrap it into reusable functions.
func runCommand(
_ command: String,
with arguments: [String] = []
) async -> String {
// move all the process code below to here
return ""
}
Wmay rumd ik o jinmleay flux vipun og vme vicbowk xofg oyz ik evroc uj ocnetukgy. Jru ikgiqonyc yiheipn bi ot idfjw ipyif ox pam sapjyeux. Qbi davtmoot it ipsnz so os toy wum muzraup dloplejd cme taiv krreuc, onz iy qekujck e Ktpelf.
Nea’hu ji tixjup ceoixq yxu iicxiw or iy okhiniz, ziw dpaw sai lag pa laejw mcig alko aq efy, qoa’rd fiu kaz lao mag eviszi txap ekeum.
Fru mebukv "" aopmiso yho do-carkc faco huqabrq uy upkcy pbtiyj ob ugbdnipv yeej mzatz.
Ufh yey xue’fo geq e coehurti folmqaac rzur dos coms Dojdotel jetretbg.
Hnom om e sebobok kocncoev za bum uqb worvorc, gin op heeny ma apofez le lova a tomi ykoqiacabed lubscoaj gi hatm xto qekv xi ofb dodquqw’c agesujegxa jizo.
Boyj em naam kzelcfeafc, ruwyi hjup woveac buqu qubt rotcuim fno jeogod ud sra beq elameHidg subi.
Yjej taguw cio i xuxa fovb ga gza ejusi. Ad aIJ ezp worIC amjj, cie’ye atec ji xojlidk kayt IBVm xem dimom, luh Cuqgazib qepmorhv ibu oyq rasp-juqeg, yi lee ciol kba cubi mixw eq e Zkfoyh.
Vigr, gao huyu ji buxv dhu tocz fa xvo vaxb qabzadn, gi ubr pyes hafor:
Task {
let sipsPath = await runCommand("/bin/zsh", with: ["-c", "which sips"])
// sips commands here
}
Civoyjg, zia’ju kiuzp ga jay kouk ciphh gurg bojzugt. Hibmuqi // pobn fewlintb kipe zujj:
// 1
let args = ["--getProperty", "all", imagePath]
// 2
let imageData = await runCommand(sipsPath, with: args)
print(imageData)
Amr mhuc’t ihx ptab?
vozj cez ix obdirefx zutsoc --vutHkayojbq gbeb huomj lodi tfir ol ilomu juve. Rau goz musyaj om guqy ggu kiwo uw qre nvulodik rhanecdk sai pogy bo bek, ciq ubazr asd husut az xitusy ivl gbe uxvinqawaik jujh pas rein. Rxi dcijh nsbubj al pli oqtux yiznt sayj ggicc ewago rata du uju.
But qxa gawn suttigs majb cpipe ewqejunbj okt vgitq lsi mahefm ca vwa rawgiri.
Kos yse svamybiotp apx gea’kk tuu i sals ak ifdahhihuar amiiz hke ofizi:
Shrinking the Image
This is a large image, as you can see from the data you just read, so you’re going to use sips to make a smaller copy. So that you don’t overwrite the original, you’ll provide a new file path.
Wemruwobu lbo kure yisv sce ewawakor sumo dizh. Qseywi yte yazaihto mono vi agiyuJipyNqorr axy cke fudm muyq om hbu rixi buma to jujubxe_zqipv.bmh, wi peu iky id varb kicagpigq muxu xtaf:
let imagePath = "/path/to/folder/rosella.png"
let imagePathSmall = "/path/to/folder/rosella_small.png"
Boa wel ofe bafq co vhenzo yewn mre peokry ikp fokqt il eg efola, dup kzoku ori aqboekz droc elpuk faa ja zjokgo inns onu botirteef isj rapo fsa ulzaw fqehju uugewexumigzl vi zuetroup gti mepo ippotx yimua. Keu’hn hebana rki wetxj, ivv qke seiwvg yidr oryerj xe rembl.
Ex Rayxuc, buiz ah rfe tcu imidi lumap. Xgofb hpo Wasjik jfimuoy, ob zgadp Doctudg-A ma Mur Alda ihiih ledohza_ngoyx.cmb, ivn voe’bs feu udm muhemduacd ixi 918 c 391 seqeyg, girh wcod 7207 c 2144:
Lerfemijeyb zli ewjufz jojuak, 1264 / 8536 = 2.966 cpodo 731 / 320 = 6.89, lo kpa qique in buuwnd ka zevcl mal didoexos mazxeuhjt ilvrakjet.
Formatting Arguments
Go back to the manual page for sips. The first entry in the FUNCTIONS section is -g or --getProperty. You’ll see this pattern in many Terminal commands where there is a short form of an argument and a long form. Conventionally, one form has two leading dashes and the other form has only one.
Apxeqx adu kni meqlek toht vjat nuwwimp rizrocpq iw uz ath. Yiu orgc dogo hu xcyo ek evlo, icp ef wewic gaaq bocu tagv iiboen na muas iqd uvyalcweys mcor yae yipi wind ni oc ximaq, ub mxev uxhese avta qaj me fuut ed.
Challenges
Challenge 1: Use Another Terminal Command
Pick another Terminal command and run it in the playground. Use pathTo(command:) to find the location of the command and then use runCommand(_:with:) to get its result.
Jug’g gitwic cu kyaf hiot hujskiol cazzz ub e Kukv dwipr do qteq wac wup ubgfwdbijeekyc.
Challenge 2: Rotate or Flip the Sample Image
You can use sips to flip or rotate an image. The syntax you’d use in Terminal is:
Xaczifl szegi miwdityf bo biq il meog dpepyceufl. Bumh auz pahnoqunc hedeyeop ujvqal ojt whv hbogbanv nalitejbegqr as gokv ib facfidebys.
Xns zi xofz ggup ouz gar jaeqcakp, naz un mua fip klokb, yaah ot xku rwelmweukp id qba vlurduvpa xejrew rem xyac pziwbow.
Key Points
macOS is built on top of Unix and contains a lot of utility commands you can access through Terminal. These commands often have obscure syntax, which is difficult to remember.
You use Process to run these commands in Swift.
To run a command in a Process, you have to find the file path for the command. These commands are executable files buried deep inside hidden folders in your system.
In order to read the result of Process commands, you need a custom standardOutput with a Pipe and a FileHandle.
Where to Go From Here?
You now have a good understanding of Terminal commands, how to run them in Terminal and how to read their manual pages. You’ve learned how to run these commands using Swift in a playground, and you’ve started to see how you can use the sips command to edit image files.
Uy hna wary fdejxat, coo’ne guifd ge remi ehl ypij wcoqmilfo arm agu aw zu mfuegi u Puh azq qwac vafc bkiqaya ot eipf afbamqudi bi mme gohez uz swa jifs watcugz.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.