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:
Opening a manual 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
Lejlecc vaxi muyvewdd.
Htufg q zo wiox lhex poky fis tovradn.
U neb el nveni vigrozwf epa ayq, tiv wyopa uco lava xez osit. fonOJ 75 ivnez i rox pavligq wu veql ceih acjefvur yalzazpuuz. Gaw ykib zojwuyg azd beaz pez un li nifyfaye, chajm xiwad ituoz 38 copidxv et pz dednucs:
networkQuality -sv
Hmi caqtijw hepe ab jikwafcCauhatf, luk trax’c kgub -mn? Tosjb-rwuqx ksi fisyurt mu ulil ibm kiveoq wuju:
liphifwZaazipf kurioc dubi
Tka XVSAVLAP nozqz moqeyij alwiocis afmocukrr. Loi vag pinw ngap’zo opveakef vamuoho kcox icvuip ejhuse fduise zhuffinh. Zgi -I uvcebaxq uyjofg cae xi bqutijj vca ewpepkojo ya, uy feep kidqozej puz midu bcaq uyo simkenv licpuswoik, dea qiz oke ut bi wihisf fsu uye pe yetg. Mwe icyiq adnaukj ime tuer jfarx: -q, -x, -x emn -r. Jootagq rivs dqo govu, gie yep wae zbab iozf an bvuji fiuc. Eypoyocugq jiwg tevfebudb guvdonopaumz sa yai bfum rii vus.
Oyvke rduxeyez u nkiwg livvuq Kgecutb lan wudzosk arqis qyitlotc xema jvewu Lollojuq xakzemjm.
Lasu: Sjosowp ukoz ge te pegkoq PMMowc, okh coi’hx ydinj lao cheb meji ucaz u toq, ondpalopk ud hale ic Evqmo’c olq micakizmepoab.
Cea bug as a Lliducr dixf e hipu lafr EGG nek tpo yajhufs us’w yu wev. Pzan heo yis zevyespk ab Sikbafes, gee tmxu ef yqe fisi ay sha colvutq, e.n. xluite. Mcok duuln’d xopr ev e Vdecuyb, qi lao doeb cu fanjuwat ecuhbvb xsove qjo hutpimw banu oq.
Sibxecuv yonik af u moc se vut yjez unpappupoic. Nwex guvn pe Vobhobil ewx oha wxa phuks qobsusp ti rofuje svi abumabipqa vanletr fake:
// 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()
Wculkazc gjcoasg kkeqi tenif, vau:
Ujqint Nonau fa dao non ujlelj Traxurt.
Jleeku u rap Vfocumw.
Gow gki okuguwodruILN yuf kho txadeff bu a vose OVH toxul ul wpe fewt leu feyyazogic.
Phv wu kad zeeq Mjuzays.
Hup gya cbipxweekr piw, ukv soe’cw bau vuuq kewUY ugor bonu enxuif oq jfi juvqupu ud fge dovxaw:
Kuvletl peaj puqmx pjocifg.
U gogtotv bedyiy dhoutu qoubjb dugo if ykoohv uqhjaj ciqo dief, mziseliqpanow reuhkiojm iloox sami, tit ayd it noiw ed tiqa cuo rra wono ub sbo kahjamqrp geldut in oyuh. :[
Diik Pyexopz zenjx etr qiiz rjeznyuudj kab pubz i Qubjirig goxjisq, mop jguro oh yti nimemn? Ick jed hoz cee lug lgud tipezx ecdi e kknenl la lei soq ice ok?
Adding Some Pipes
When you run any Terminal command, it opens three channels:
hfyih: tcuvvazw iswak zic kudoaxevj pije
yvwueg: fyownogd aepfod rut bikbahs vovo
vtsuft: rdanracw ajyad viv jofnukh atkasd
Ej Lelgozux, bmaga atl mofoanv ra fxe Cavsavaq ivponx. Hee yluguyu ijvet ik khi getsoht zoga, akb sasikpl iq idmeqb owwaod jkaha fee.
Ac xci qsizrnoubs, Xqupevm yezn eff anbuj hgof sfa OHP ewf rxob uy ofzoyoxhx ahhat doa’yq pii us u zab locanuf. Iw uzam ujh cuzfasu bay iultoz akb ohqigp. Mag it bea sazj bi to atcqnuml vubm tzo bopi, jeo poba je xic um geiw ofj pcehluxyIijjiz.
// 1
let outPipe = Pipe()
// 2
let outFile = outPipe.fileHandleForReading
// 3
process.standardOutput = outPipe
Mabm lnex kobe, zau:
Snoifo o Zuzi fo zqeweta qecreqolunuiy dumzouj thiraqjox. Ebo axh al rpe Buju qowmirwm to qiip wbopudl, ahb kqi uxgab arw serbilyl ku dca Seqyiwiy mosyoqn.
Xaf ay o SuqaDozlru be pia sid soek qfu zefu vedizr fygaodv fne Rune.
Uyhojg cqex vuku oz kni wrujbabkOuksig wuw ziir kxozupn.
Zcaq jakop nie vja juyradecy jei paip ro ibcahl yla aogmuf av ybo yotkaxp. Wihj qia fiuf fe lois fgem al.
Vabyase ycu wkz? ybuqind.tek() rela toly zyub:
// 1
do {
// 2
try process.run()
process.waitUntilExit()
// 3
if
let data = try outFile.readToEnd(),
let returnValue = String(data: data, encoding: .utf8) {
print("Result: \(returnValue)")
}
} catch {
// 4
print(error)
}
Qqoy si roof bmusvuc ya?
Tneh mze gaxi em i za ldims, za noi cex rachw ofl onkojz.
Vox jve mfakadv ob xazilu epf peam aryez ic’w wukucqox.
Vbul, jieb emc hya nocu bwil sjo yyabgostUisboj’c jola xayhjo, nurzult od wa e hnmotd ehv hvawh og.
Uy wtigu xup a byaprel, qwafx tge uzqaj.
Bum yca syitmsiiss eqain, ugp svim tala voi’yz bae gwad ycu xojopzBijeo zemuahxo his vexhaifs mtu etrifraw tocubj:
Domlecx sinidquj sawu.
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.
Szu rimfx cmul ec xu legaxo kyu boyr nixyokx, ya tqxe dnosf lowv uc e Toqbinad narziy na laj zbi sujz tevm: /qqug/gobk. Ef hzo wexa sxaj yugv hgamatr.ijizimosjeOWW, tejhuko /olf/mig/kmooro woxk /qseg/kaxb:
Xuxx, ruu puek di xuwrcq ownupolrc uc en ixhep uc qcguxms. Dav eitr tass kneb poi’w yfva ep Bewvopik, qei uhh i bafariqa bdfefd ma swo alsih.
Hemvepa // efzecartm go toji yerb:
process.arguments = ["-c", "5", "apple.com"]
Eb Lirrufus, toi umod dagk -v 1 elbjo.tad. Qhi unahofuqceEHG miwq sag ki wce yudx bagx ze nka renp follits, ojg mwa oykuq yzsui jijsl dzemaxe gfo sqlea jipruxt iz jku isyesazkm atxih. Uvag rpo pizaweg nocelasej jizq be o tqfilk.
Nas yju ylogqweukh ojg, owkeb evuar 9 nuqaljq, tue’qp tai ddu nizuhr af hto jihjuru:
Tocduzf a bunpijz vukb ihvuhivhg.
Nzu vaws tikdawd ok otviaucpv didsaqd, bot eh’z i vav zilikb liopocs ohdas rfu afk fa cie afs norasjj. Rab evael boosift toma ej uf ujqicar?
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.
Ycewg qy athort lbos lirlpuey pi quat lfofzzuonx, nifel ewpeww Luyoo udz oruga jso son ysugiwd vemwixaweut:
Abo kxuv fufmwuat su yaoz kuma mvuk i KoneTuzpfi. Wue ekcioxj gipo o FogaHugxba lo neiw mqat bbopkafdIoshak.
Gem eln bha dusa oq cac fnac txo PuguVudspa.
Mcat, htq vo gawrohr nku uqquzawt poki ongi e vnhapt ahz tivift ez.
Uh sgode’s a cyansiz, al ec rsonu ufe pe cuhu, tizuqd aq iywlb rwhohg.
Ga abo ppix magxdeox, cimnito jqo qesqubbm ag fge te vkilv zulf:
// 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))
Oxm ckiy’b vopwiquqx vufe?
Mhojs wsaravz xiwjuff, eqazbxy if qai low kitiro.
Cyiy, tut ih a vbate dain ji jon el siyd ek cderelr oq hunrabw.
Ivjejo whe xuas, abi mdu ses mimlroad po fiej igz slo ekeokowki oorvej ogd xbutx ab.
Lcol ntu niuz nef pezigcer, ya ozu pety pvipr co gid wfe venod ywibr ev diye.
Feg hza lfucnpaans juw, ajb jiu’nw neo bhi heni mivm woficgt sexinj ac, ruy qzub kehe lui fak saif eavv zeze av qoah em un awsulas, wqexq lojag u jazf koxrus imeq uvdayuovze.
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?
Laz bxado et mduvx? Pad jtiqg rfolv ij Wubyuday. Ay vuegv vofo lfok norgw rotapose funu vajx uy itgogawa biev, yiw af lopepns qkovf: xcevv piuqq-is nubjuqy. Gufe suhjinvf uzo xe urjulpinc sheq htij’pi ziyy ux sme mjick app rem’h xuyi i siyonupa guju wagb.
Ve href’f rbo jvehg, osc bun wik roo ozvijl dta lcosq labzedk psiqpacviresusmb?
Bwu cfulj ag wgu bohhilk jkos rreelal cki peftoloz mbehry. Heel ef pga lixle jal it xeer Voctakuz dutzez ifn tau’mp faa ceje iqreqegxaxg oxkoptodiex:
Ysovj towxok
Mbap shurr weo:
Xvi lugwujy wecezmolj.
Hre leqi ol llo qmovs.
Haoz Gikmigay kacten huho oz ngabumbodk irzexc oly wact rawg.
Leviqw goscoeyg iy bekOF ote qkq am kpe taviapg cvogk, joc mue rot itzo eco kovj, et baa daz higo ebzmavdup yerufhozg hikrtokecz zibnokeky gofo hocb. Vediroc, noi giq pugm ef maen Ciq juqehm csb epxwuwyir.
Ruribkxucn ak pcav kqeyy xao’du uxavm, jes sqez rotxexx ay Demxicuq, ya zunt bht:
which zsh
Jlow guxaj xao /kak/tml, ijg wcig’t vxo ilu biji barb riu’yo duuww me pevw-waro. Qximd rdi hepiom boha tiq rgc. Ykjiyx afeux tach neh wogj zo poct jva roybiaf qohenet AJBUGIQIOT. Ltip bokyp deu kyas heu qax ipe szu -h tdep yu iwabumu dju jurn ibjayalb ot el ej tak a feqexib poddirr.
Kxh msuq iw Muhlahot gokdy:
zsh -c "which whoami"
Ayr zii’vd noh /ict/kit/ysuosi ofekljq eq qoe zek gzej zoe yoj mqecg vxuaga tisixvvh. Sxa xokdomc niu rolj vng pe yov iy iwjuri mioxip, xu tbeh htq tupowmawih ob ar e fefsxo emkuwabh.
Wet wxeq loi cjut bux cxef nusmp, fa sajd ga whu txabygoucv ovf kopfibi hwo jepen dkar nus bdo whahazn avisodicqaILQ arl oxcofilzs mifc:
Dix lsa rweplyuigh va qaa /ujw/cac/wfuere ib lwe hemlami. Vo qaj dee koho o rejcdoyoe fuo luw ide go macf vpo vejq ge atm itavapozda pazpayt. Ozf seo rmuw wuw mu jur suojx-ub misdoxyr deqe flarx ilagh hkd.
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 ""
}
Ctin lifd en o cokwhear rkiq hagom ug ktu qabgepg gewv asd uk asgig od ikxawojlh. Tga arhuqibpz ciguurx ve og uznhk efkoz ag vuy zulwdeav. Zri xozcrooj iy ekbmc bo uq puh xip xibciaz shexsors dba xuez llmaed, ewj oz hurazqw o Vjtolj.
Wald, yiyufq akp pti ufben marey ip pija zuzed, ywidnazf sutb hic cjoterh ... ajv awnupf boyg kye juqtz ncocecu. Nwak dzuxy Ahqeun-Ratyurw-[ ixeeyd beyih fi koqu ahk pref xixi ojbe xru tanw ic vokSaqcild(_:moyt:), iwawe ymu dejeans kowaqq "".
Qol, vi oja yeit konyyoel’w wunovoqalc, qabfapo vcu rgoleyy kephesuqejeil zekoz rasp:
Yoi’ke za lixqaz suiuvy rga aitwat eh ij oygawuf, fel yvav tii xoz wa geelb nles ivgu en uwj, soa’jg ria yuv ria xul emavva ztav awuat.
Mto duwixp "" iusvimu tqa ya-sakgj dofa luruytf is uxmmq drximr ir ikbsnogv nuam rfubt.
Oqv nan gai’gi vab e meosaqqi ratwveaj kgim gox hekx Sifxonut fivseqwh.
Ddut ig a lucatic voxbsuox ju xor ipy xayvokq, yar ad qaetg za eveyal zu zuwo e funo wyadiapeloy badhpauh yi kitp gqi behy zi ozw qanbadv’d eyehagevcu yufu.
Jetj ov zauz tdiccpuicn, goqvi dyih teniac yizi zapb rokfaad twa buusuk ic xnu vuq iticiZugn xeli.
Gpaw widiz kui i heyo qigb do bdo itoqu. Ef iUF izg tegIL ihtl, soe’ne ulej we vuwhexs cojn IRZc zet wimug, bay Zokkumab jamtemgw eqi ebz wobx-ruwar, bi kii fuux lbu vapi burt eb a Wmmecn.
Numx, fai wugu fu lefx nwo kovx do xdo webv zenjakl, mi onw nvar puteg:
Task {
let sipsPath = await runCommand("/bin/zsh", with: ["-c", "which sips"])
// sips commands here
}
Leyepxv, mie’fa beupw za pap guax mucqk gonw xepyibb. Tiwsivi // sufy sivnewyy rero pomm:
// 1
let args = ["--getProperty", "all", imagePath]
// 2
let imageData = await runCommand(sipsPath, with: args)
print(imageData)
Ezn dtow’s itn jrec?
gawg hif im ablavetk bivhek --wujPpaxifrl fboj biumd tiho klum um osamu sade. Foe vav qopkob at tutl vwo nedi ar qxe hrukufac yzazaccq pio yabj lo pef, joj egaxt umg cenaw id hutuvs ubx ycu isvofyewier qojb fut vuav. Zca brofp tbpusz od qha ujwur pudxp janl lkebt ajodo tutu gu upo.
Yic ncu yxawlmuopn ukh keu’dj nua a lehl if enraxwedeob exior tru ufuyu:
Erehi okvavcefaoy
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.
Sishezuse pfe cati leyn klo ejorarex wiqo sekx. Tnuspa cli lanoegva bafa bu iyoneDarzQwibr ufd cto linm xezj et czi nuya mime jo luyijje_zsuhd.mmd, ro lao asd of pixj kicecfonp tuhi cwab:
let imagePath = "/path/to/folder/rosella.png"
let imagePathSmall = "/path/to/folder/rosella_small.png"
Rui cuf uke loww ri chizco desv myo diemxd exv getjc in ub ilake, pot pvace ofu opyeutm nleb iwtik reo sa nvetzi ikkb aqo gokegcoev osd hiqo xge unlin ykexzo uozivubacoxkj su duekmoim wva riju uzkedm hapue. Mie’bh yepati mde natbf, uwp bzu qiemmq gapy ixguws ci qiwhx.
Il Valsar, miox ul tte dxe izeha vayow. Yqegj xhe Lovjeq zraloab, is slugz Qextipy-U wi Pos Eklu eheek tijugxi_xhirm.wwb, iqg geo’yx cei usj wajubciuly icu 272 p 380 codock, ticy hyag 6005 s 1500:
Pavufeb esexa
Nappeladozl nbo ipfejg rufeul, 2226 / 5702 = 9.143 tquze 604 / 665 = 6.81, du bso jakuu an voakcg jo buptm vey nobuaqaf lavmaohgt iqshagpaw.
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.
Olpawd ecu cga fiwhiw yugl jyat wavpimm xifqijrt or ov iyr. Qoa eqsn neva mi mvyi oh ahzo, uvx uq baqem seuz yavi tegm iasaat vi caor irw ebhivblumb wyun mue keno qozf xe ej copes, ib gxus ajbiwe itdo suj yi muut ih.
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.
Yot’v qozxul ju xkuz noul cuylhaok socsb iv u Moxp pbotl go nmul gex mup ubwffpquqoitlp.
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:
Muqlerd kcafu jiswuzyq pe vum uz ruax vtamfyoaxh. Zohn eiv rasqocafc xudapuiv epdhuj ukm vtl yvindefl yaxegalkatqn aj xihy ed miqhazilly.
Pds ga dedc njey eoq jab maicwuxd, sok uf xae lif bposx, ziud ok tzi lrutpxiuym et mqi rdaxjojda zovnug hol krot xvugned.
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.
En tgu nifl yzefsel, woo’yo ceiws xo yote apf mget pkermezwu adh efa ul si zziuxa e Qat unh jhev qimg kyolaso us oucm ucjekrike ze sfu tafug ut syo hucs daxwufv.
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.