Shell Araçları ve Scripting

Bu derste, komut satırında sıklıkla kullanacağınız en yaygın komutları içeren bazı shell araçları ile birlikle bash’i bir scripting dili olarak kullanmanın temellerini işleyeceğiz.

Shell Scripting

Şu ana kadar shell’de komutları nasıl çalıştırdığımızı ve bu komutları nasıl birbirine bağladığımızı (pipe) gördük. Fakat, birçok senaryoda komutları bir seri şeklinde çalıştırmak ve koşullu ifadeler ya da döngüler gibi akış kontrolü deyimleri kullanmak isteyeceksiniz.

Bir adım daha karmaşık hale gidersek, sırada Shell scriptleri var. Birçok shell kendi değişkenleri, akış kontrolü ve kendi dil yapısına sahip bir scripting diline sahiptir. Shell scriptlemeyi diğer scriptleme dillerinden farklı kılan şey özellikle shell ile alakalı görevler için optimize edilmiş olmasıdır. Bu yüzden, komut boru hattı oluşturma, sonuçları dosyaya kaydetme ve standart girdiden okuma gibi işlemler shell scriptingde en temel yapı taşlarıdır. Bu da genel geçer scriptleme dillerine karşı büyük bir avantaj ve kolaylık sağlar. Bu derste, en yaygın shell scriptleme dili olan bash’e odaklanacağız.

Bash’te bir değişkene atama yapmak için foo=bar komutunu kullanıyor ve değişkenin değerine $foo komutu ile ulaşıyoruz. Dikkat etmek gereken bir nokta, foo = bar komutunun çalışmayacağıdır. Çünkü boşluk bırakıldığında shell bu komutu = ve bar argümanlarına sahip foo programını çalıştırmak şeklinde yorumlayacaktır. Genellikle, shell scriptlerinde boşluk karakteri argümanları ayırma görevini üstlenir. Bu durum başlangıçta biraz kafa karıştırıcı gelebilir, bu yüzden her zaman dikkat edin.

Bash’te stringler ' ve " karakterleri ile tanımlanabilir, fakat bu iki yöntem birbirine eşlenik sonuçlar doğurmaz. ' karakteri ile tanımlanan stringler gerçel stringlerdir ve değişken değerlerinin yerine geçmezler. Fakat " ile tanımlanan stringler değişken değerlerini gösterirler. Aşağıdaki örnek bu durumu açıklamaktadır:

foo=bar
echo "$foo"
# prints bar
echo '$foo'
# prints $foo

Birçok programlama dili gibi bash scripteme dili de akış kontrolü deyimlerini desteklemektedir. Bunların arasında if, case, while ve for gibi yapılar bulunmaktadır. Benzer şekilde, bash argüman alan ve bunlarla işlemler yapabilen fonksiyonlara destek sağlamaktadır. Aşağıda yeni bir dizin oluşturup, onun içine cdleyen bir fonksiyon örneği bulunmaktadır:

mcd () {
    mkdir -p "$1"
    cd "$1"
}

$1 script/fonksiyona verilen ilk argümanın yerine geçmektedir. Diğer scriptleme dillerinin aksine, bash argümanlara, hata kodlarına ve gerekli bazı diğer değişkenlere işaret etmek için birçok özel değişken kullanır. Aşağıda bunlardan bazıları bulunmaktadır. Daha kapsamlı bir liste bu linkte bulunabilir.

Komutlar genellikle STDOUT‘u çıktılar için, STDERR‘i hatalar için kullanır ve bir Dönüş Kodu kullanarak da hataları scriptleme için daha kolay hale getirecek şekilde belirtir. Dönüş Kodu ya da çıkış statüsü script/komutların işlemlerin nasıl sonuçlandığını kullanıcıya iletme yoludur. 0 değeri genellikle her şeyin iyi gittiği; 0’dan başka değerler de bir hatanın oluştuğu anlamına gelirler.

Çıkış kodları ileriki komutları koşullu ifadelerle çalıştırabilmemizi sağlar. Bunun için ikisi de kısa-devre operatörü olan && (ve operatörü) ve || (veya operatörü) kullanılabilir. Ayrıca farklı komutlar ; kullanarak aynı satırda da yazılabilir. true (doğru) bir program her zaman 0 dönüş koduna sahiptir ve false(yanlış) bir komut ise her zaman 1 dönüş kodunu döndürür. Şimdi bazı örneklere bakalım.

false || echo "Aaa, çalışmadı"
# Aaa, çalışmadı

true || echo "Çıktı yok"
#

true && echo "Her şey süper"
# Her şey süper

false && echo "Çıktı yok"
#

true ; echo "Bu her zaman çalışacak"
# Bu her zaman çalışacak

false ; echo "Bu her zaman çalışacak"
# Bu her zaman çalışacak

Genelde sıklıkla yapılmak istenen bir başka işlem ise komutların çıktılarını bir değişken şeklinde kullanmaktır. komut değişimi bu iş için kullanılabilecek bir yöntemdir. $( CMD ) nerede kullanılırsa kullanılsın, CMD komutunu çalıştıracak, komutun çıktısını alacak ve kullanıldığı yere bu çıktıyı yazacaktır. Örneğin, eğer for file in $(ls) komutunu çalıştırırsanız, shell ilk önce ls komutunu çağırıp sonra gelen değerler üzerinde bir döngü oluşturacaktır. Daha az bilinen benzer bir özellik ise işlem değişimidir, <( CMD ) komutu CMD komutunu çalıştıracak, çıktıyı geçici bir dosyaya yerleştirecek ve <() kısmı yerine dosyanın ismini yazacaktır. Bu method özellikle STDIN yerine dosyaları argüman olarak bekleyen komutları kullanmak için oldukça faydalıdır. Örneğin, diff <(ls foo) <(ls bar) komutu foo ve bar dizinlerindeki dosyalar arasındaki farkları göstermektedir.

Çok fazla şeyin üstünden geçtik. Bu özelliklerin bazılarını görebileceğimiz bir örnek görelim şimdi de. Bu örnekte verdiğimiz argümanlar üzerinden yineleyip, foobar stringini grepleyip , eğer bulunmazsa dosyaya bir yorum olarak ekleyeceğiz.

#!/bin/bash

echo "$(date) tarihinde program başlatılıyor" # Tarih yerine yazılacaktır

echo "$$ pid kodlu $0 programı $# argümanlarıyla çalıştırılıyor"

for file in $@; do
    grep foobar $file > /dev/null 2> /dev/null
    # Eğer verilen kalıp bulunmazsa , grep 1 çıkış koduna sahip
    # STDOUT ve STDERR ile işimiz olmadığı için onları null bir registera yönlendiriyoruz
    if [[ $? -ne 0 ]]; then
        echo "$file dosyasında foobar yok, ekleniyor"
        echo "# foobar" >> "$file"
    fi
done

If içindeki karşılaştırmada $? değerinin 0 olmadığını kontrol ettik. Bash bunun gibi birçok karşılaştırma sunuyor - test linkinde detaylı bir liste bulabilirsiniz. Bash’te karşılaştırma yaparken, tekli köşeli parantezler [ ] yerine ikili köşeli parantezler [[ ]] kullanmayı tercih etmeniz daha iyi olabilir. sh şeklinde bir script oluşturulmayacağına rağmen bu şekilde hata yapma ihtimaliniz azalmaktadır. Burada daha detaylı bir açıklama bulabilirsiniz.

Scriptleri çalıştırırken, genellikle benzer argümanlar sağlamak isteyeceksiniz. Bash bunu birkaç farklı şekilde kolaylaştırıyor. Bunlardan biri de dosya ismi genişlemesi kullanmak. Bu tekniklere shell globbing adı verilmektedir.

convert image.{png,jpg}
# Alttaki hale genişleyecektir
convert image.png image.jpg

cp /path/to/project/{foo,bar,baz}.sh /newpath
# Alttaki hale genişleyecektir
cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath

# Globbing teknikleri beraber de kullanabilir
mv *{.py,.sh} folder
# Tüm *.py ve *.sh dosyalarını taşıyacaktır


mkdir foo bar
# Bu komut foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h şeklinde dosyalar oluşturacaktır
touch {foo,bar}/{a..h}
touch foo/x bar/y
# foo ve bar dizinlerindeki dosyalar arasındaki farklar
diff <(ls foo) <(ls bar)
# Çıktılar
# < x
# ---
# > y

bash scriptleri yazmak ustalık ve dikkat isteyebilir. Shellcheck gibi araçlar sh/bash scriptlerinizdeki hataları bulmanıza yardımcı olabilirler

Terminalden çalıştırılabilmek için scriptleriniz bash dilinde yazılmak zorunda değildir. Örneğin, aşağıda argümanlarını ters çevrilmiş şekilde yazdırılan basit bir Python scripti görülmektedir:

#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
    print(arg)

Kernel bu scripti bir shell komutu yerine bir Python interpreteru kullanarak çalıştıracağını bilmektedir çünkü scriptin en başındaki satırda bir shebang bulunmaktadır. Shebang satırlarını scriptlerin taşınabilirliğini artıran, komut sistemde nerede ise orayı bulabilen env komutu kullanarak yazmak iyi bir uygulamadır. Lokasyonu bulmak için, env komutu ilk derste tanıtılan PATH ortam değişkenini kullanır. Bu örnekte shebang satırı #!/usr/bin/env python şeklinde gözükecektir.

Shell fonksiyonları ve scriptleri arasında aklınızda tutmanız gereken bazı farklılıklar şu şekildedir:

Shell Araçları

Komutların nasıl kullanılacağının bulunması

Bu noktada, mahlaslama(aliasing) bölümünde kullanılan ls -l, mv -i ve mkdir -p gibi işaretleri(flag) nasıl bulabileceğimizi merak ediyor olabilirsiniz. Genel olarak herhangi bir komut için komutun ne yaptığını ve farklı seçeneklerinin neler olduğunu nasıl öğreniriz? Elbette googlelamaya başlayabilirsiniz, fakat UNIX StackOverflow’dan önceden beri var olduğu için, shell içinden bu bilgilere ulaşmanın yolları var.

Shell dersinde gördüğümüz üzere, ilk akla gelen yöntem bahsedilen komutu -h veya --help işaretlerini kullanarak çağırmak olabilir. Daha detaylı bir yöntem man komutunu kullanmak olabilir. Manual için bir kısaltma olan, man belirttiğimiz komut için bir manual sayfası (manpage olarak da adlandırılır) sağlar. Örneğin, man rm, rm komutunun nasıl çalıştığını, ne yaptığını ve daha önce gösterdiğimiz -i işareti gibi alabileceği işaretleri çıktı olarak verir. Aslında, şu ana kadar verdiğimiz linkler komutların Linux manual sayfalarının çevrimiçi haliydi. Native olmayan, kullanıcı tarafından yüklenen komutların bile eğer geliştirici yazıp yükleme sürecine dahil ettiyse bir manpage’i olabilir. ncurses vb. tabanlı interaktif araçlar içinse, komutların yardımına genellikle program içinde :help komutu veya ? yazarak erişilebilir.

Bazen manpage’ler komutların fazlaca detaylı tanımlamalarını içerebilir, böylelikle yaygın kullanımlar için hangi işaretleri/sözdizimini kullanacağını kavramak zorlaşabilir. TLDR sayfaları bu soruna tamamlayıcı zekice çözümlerdir. Bir komutun kullanım senaryolarından örnekler vererek hangi durumda hangi seçeneğin kullanılması gerektiğinin kolayca anlaşılmasını sağlar. Mesela kendimi, tar ve ffmpeg programları için daha çok manpage’ler yerine tldr sayfalarını kontrol ederken buluyorum.

Dosyaları bulmak

Her programlamacının karşılaştığı en sık tekrarlayan işlerden biri dosyaları ve dizinleri bulmaktır. Tüm UNIX ve benzeri sistemler dosyaları bulmak için harika bir araç olan find paketiyle birlikte gelir. find bir kritere göre dosyaları özyinelemeli şekilde arar. Bazı örnekler:

# Tüm src isimli dizinleri bul
find . -name src -type d
# Konumunun içinde test geçen tüm python dosyalarını bul
find . -path '**/test/**/*.py' -type f
# Son gün içinde değiştirilen tüm dosyaları bul
find . -mtime -1
# 500k'dan 10M'a kadar olan tüm zip dosyalarını bul
find . -size +500k -size -10M -name '*.tar.gz'

Dosyaları listelemenin yanında, find ayrıca sorgunuzla bulduğunuz dosyalar üzerinde komutlar çalıştırabilir. Bu özellik oldukça monoton işlerin basitleştirilmesi için inanılmaz şekilde faydalıdır.

# .tmp uzantılı tüm dosyaları sil
find . -name '*.tmp' -exec rm {} \;
# Tüm PNG dosyalarını bul ve JPG'e çevir
find . -name '*.png' -exec convert {} {.}.jpg \;

find oldukça sık kullanılmasına rağmen, sözdizimini hatırlamak bazen hatırlaması zor olabilir. Örneğin, bir kalıpla eşleşen dosyaları bulmaya çalışırken KALIP, find -name '*KALIP*' (veya eğer kalıbın büyük/küçük harf ayrımı yapmamasını istiyorsanız -iname) komutunu çalıştırmalısınız. Bu senaryolar için mahlaslar(alias) oluşturabilirsiniz, fakat shell felsefesinin bir kısmı da farklı alternatifleri değerlendirmenin iyi olduğudur. Unutmayın, shellin en iyi özelliklerinden biri sadece programlar çağırıyor olmanızdır, böylece bazıları için farklı alternatifler bulabilir, hatta kendiniz alternatifler yazabilirsiniz. Örneğin, fd finda basit, hızlı ve kullanıcı dostu bir alternatiftir. Renklendirilmiş çıktılar, regex eşleştirme ve Unicode desteği gibi güzel özellikleri varsayılan olarak sunar. Bana göre, ayrıca, daha içgüdüsel bir sözdizimine sahiptir. Örneğin, bir KALIPı bulmaya çalışıyorken sözdizimi fd KALIPtır.

Birçok kişi find ve fdnin iyi olduğunda hemfikirdir, ama bazılarınız her seferinde dosyalar için arama yapmanın daha hızlı arama yapmak için bir tür index veya veritabanı oluşturmaya göre verimliliğini sorguluyor olabilir. locate komutu tam olarak bunun içindir. locate, updatedb ile güncellenen bir veritabanı kullanır. Birçok sistemde, updatedb, cron kullanılarak günlük olarak güncellenir. Yani, ikisi arasında hız ve güncellikten birinden vazgeçilir. Buna ek olarak, find ve benzeri araçlar ayrıca dosya boyutu, düzenleme tarihi, dosya izinleri gibi özellikler üstünden de dosyaları bulabilirken, locate sadece dosya ismini kullanır. Daha detaylı bir karşılaştırma burada bulunabilir.

Kodu Bulmak

Dosyaları isimleriyle aramak faydalı, fakat en az bunun kadar sık dosyaları içeriklerine göre de bulmak isteyeceksiniz. Yaygın bir senaryo, bir kalıbı içeren tüm dosyaları ve bu dosyalar içinde bahsedilen kalıbın nerede geçtiğini bulmak istemektir. Bunu başarabilmek için, çoğu UNIX ve benzeri sistemde verilen metinde kalıp eşleştirmesi yapmayı sağlayan jenerik grep komutu bulunmaktır. Oldukça değerli bir shell aracı olan grep komutunu veri işleme dersinde oldukça detaylı işleyeceğiz.

Şu anlık, grep komutunun birçok işaretçisiyle(flag) birlikte çok yönlü bir araç olduğunu bilmeniz yeterlidir. Benim sıklıkla kullandıklarım arasında, -C işaretçisi eşleşen satırın etrafındaki Contexti yazdırmak için kullanılır, -v ise eşleşmenin tümleyenini(inverting) almak için kullanılır, kalıpla eşleşmeyen tüm satırları yazdırma şeklinde. Örneğin, grep -C 5 eşleşmenin olduğu satırın 5 üst ve 5 altındaki satırları gösterir. Birçok dosya arasında hızlıca arama yapmak istediğimizde de, -R işaretçisi özyinelemeli şekilde (Recursively) dizinlerin içine girip eşleşen stringler için dosyaları aramayı sağlar.

Fakat grep -R birçok şekilde geliştirlebilir,.git klasörlerini yok saymak, çoklu CPU desteği vb. gibi. Birçok grep alternatifi geliştirilmiştir; bunların arasında ack, ag ve rg bulunmaktadır. Bunların hepsi şahanedir ve az çok aynı işlevi görmektedir. Şu an hızı ve içgüdüsel olması sebebyile ripgrep (rg) kullanmaktayım. Bazı örnekler:

# requests kütüphanesini kullandığım bütün python dosyalarını bul
rg -t py 'import requests'
# Shebang satırı içermeyen tüm dosyaları bul (gizli dosyalar dahil)
rg -u --files-without-match "^#!"
# Tüm 'foo'ları bul ve sonraki 5 satırı yazdır.
rg foo -A 5
# Eşleşmenin istatistilerini yazdır (eşleşen dosyalar ve satırların sayısı)
rg --stats PATTERN

Dikkat edilmesi gereken nokta find/fd‘de olduğu gibi, bu problemlerin bu araçlardan herhangi birini kullarak çözülebileceğinin farkında olmaktır. Kullandığınız spesifik aracın çok da bir önemi yoktur.

Shell komutlarını bulmak

Şu ana kadar nasıl dosyaları ve kodları bulabileceğimizi gördük. Fakat shell’de daha fazla zaman harcadıkça, bir noktada kullandığınız spesifik komutları bulmak isteyebilirsiniz. Bilinmezi gereken ilk nokta üst ok tuşunun en son çalıştırdığınız komutu size vereceğidir, ve bu tuşa daha fazla bastıkça yavaşça shell geçmişinizde ilerleyebilirsiniz.

history komutu ise shell geçmişine programlanabilir şekilde erişmenizi sağlar. Bu komut shell geçmişinizi standard çıktıya yazdıracaktır. Eğer geçmiş içinde arama yapmak istersek, history komutunun çıktısını grepe bağlayıp(pipe) kalıplar ile arama yapabiliriz. history | grep find komutu geçmişte kullanılan “find” altdizgesini içeren komutları çıktı olarak verir.

Birçok shellde, Ctrl+R kullanarak geçmişiniz içinde geriye doğru arama yapabilirsiniz. Ctrl+Ra bastıktan sonra, geçmişinizde aramak istediğiniz diziyi yazabilirsiniz. Daha da fazla bastıkça girdiğiniz diziyi içeren eşleşmeler içinde ilerleyebilirsiniz. zshde YUKARI/AŞAĞI tuşlarını kullanarak da bu fonksiyon aktifleştirilebilir. Ctrl+R üzerine güzel bir ekleme fzf kısayolları kullaranak sağlanabilir. fzf genel amaçlı, birçok komut ile kullanılabilen bir bulanık arayıcıdır (fuzzy finder). Burada bulanık şekilde geçmişinizde eşleştirme yapıp sonuçları kullanışlı ve görsel olarak hoş bir şekilde sunar.

Kullanmayı gerçekten çok sevdiğim komut geçmişi ile alakalı bir diğer özellik de geçmiş-tabanlı otomatik öneriler. İlk olarak fish shell ile sunulan bu özellik dinamik olarak şu anki komutunuzu aynı şekilde başlayan en son yazdığınız komuta otomatik olarak tamamlar. zsh‘de aktif edilebilir bir özellik olarak bulunmaktadır ve shell kullanım kalitenizi gerçekten arttırabilecek bir hile olabilir.

Son olarak da, akılda bulunması gereken bir özellik eğer bir komuta boşluk karakteri ile başlanırsa, onun shell geçmişine eklenmeyeceğidir. Bu özellik özellikle şifreler veya hassas bilgiler içeren komutlar kullanırken kullanışlı olmaktadır. Eğer baştaki boşluğu koymayı unuttuysanız, her zaman .bash_history ya da .zhistory dosyalarını manuel olarak düzenleyerek bir girdiyi silebilirsiniz.

Dizinler Arasında Hareket Etme

Şu ana kadar, zaten işlem yapmak istediğiniz dizinde olduğunuzu varsaydık. Peki, dizinler arasında nasıl kolaylıkla hareket edebiliriz? Bunu yapmanın birçok kolay yolu var. Mesela shell mahlasları(aliases) oluşturmak veya ln -s komutuyla symlinkler oluşturmak gibi. Fakat aslında geliştiriciler bu probleme oldukça zekice ve sofistike çözümler geliştirdiler şu ana kadar.

Bu kursun genel temasında olduğu şekilde, çoğunlukla en yaygın durumları optimize etmek istersiniz. En sık ve/veya en son erişilen dosya ve dizinlere erişmek fasd ve autojump gibi araçlarla yapılabilir. Fasd dosyaları ve dizinleri frecency ölçütüne göre sıralar. Frecency, sıklık(frequency) ve yenilik(recency) kelimelerinin birleşmesiyle oluşmuştur. Varsayılan olarak, fasd, z komutu kullanarak frecent bir dizinin isminden bir parça kullanarak dizine cdlemenizi sağlar. Örneğin, eğer /home/user/files/cool_project dizinini sıklıkla kullanıyorsanız, z cool yazarak kolayca buraya zıplayabilirsiniz. Autojump kullanırkense, aynı dizin değişimi j cool yazarak yapılabilir.

Dizin yapılarına hızlıca kuşbakışı bakabilmek için birçok kompleks araç bulunmaktadır. Bunlardan bazıları: tree, broot veya nnn veya ranger gibi tam ölçekli dosya yöneticileri

Örnekler

  1. man ls sayfasını okuyun ve aşağıdaki şekilde dosyaları listeleyen bir ls komutu yazın.

    • Tüm dosyaları içerir, gizli dosyalar dahil
    • Dosya büyüklükleri, kullanıcı dostu şekilde yazdırılmalıdır (örneğin 454279954 yerine 454M)
    • Dosyalar yeniden eskiye doğru sıralanmalıdır
    • Çıktı renklendirilmelidir

    Örnek çıktı aşağıdaki şekilde görünebilir:

     -rw-r--r--   1 user group 1.1M Jan 14 09:53 baz
     drwxr-xr-x   5 user group  160 Jan 14 09:53 .
     -rw-r--r--   1 user group  514 Jan 14 06:42 bar
     -rw-r--r--   1 user group 106M Jan 13 12:12 foo
     drwx------+ 47 user group 1.5K Jan 12 18:08 ..
    
  2. Aşağıdaki işlemleri yapan marco ve polo komutlarını yazın. marcoyu çalıştırdığınız her zaman, şu an içinde olduğunuz dizin bir şekilde kaydedilir, sonra polo komutunu çalıştırdığınızda, nerede olursanız olun, polo, marco komutunu çalıştırdığınız dizine geri cdler.

Debug işlemini kolaylaştırmak için kodunuzu ayrı bir marco.sh dosyasına yazıp source marco.sh komutunu çalıştırarak tanımlamaları shellinize (tekrar) yükleyebilirsiniz.

  1. Nadiren başarısız olan bir komutunuz var diyelim. Debuglayabilmeniz için çıktısını yakalamanız lazım ama başarısız olan bir sonuç almak için tek tek çalıştırmak zaman kaybettirici oluyor. Aşağıdaki scripti başarısız olana kadar çalıştırıp standard çıktıyı yakalayan ve hataları bir dosyaya yönlendirip en sonunda her şeyi çıktı olarak veren bir bash scripti yazın. Bonus Puan: Kaçıncı çalıştırma sonucunda scriptin hata verdiğini gösterin.

     #!/usr/bin/env bash
    
     n=$(( RANDOM % 100 ))
    
     if [[ n -eq 42 ]]; then
        echo "Bir yerlerde hata var"
        >&2 echo "Hatta sihirli sayıları kullanmaktan kaynaklanıyor"
        exit 1
     fi
    
     echo "Her şey plana göre gitti"
    
  2. Derste gördüğümüz üzere find komutunun -exec işaretçisi aradığımız dosyalar üzerinde işlem yapmak için oldukça güçlü bir araç. Peki, bulduğumuz tüm dosyalar üzerinde bir işlem yapmak istesek, mesela hepsinden bir zip dosyası oluşturmak gibi? Şu ana kadar gördüğünüz üzere komutlar hem argümanlarından hem de STDIN’den girdi alabiliyorlar. Komutları birbirine bağlarken (piping), STDOUT’u STDIN’e bağlıyoruz, fakat tar gibi bazı komutlar argümanlardan girdi alıyorlar. Arada bir köprü olmak için xargs komutu STDIN’i argümanları olarak kullanarak komutu çalıştırır. Örneğin ls | xargs rm şu anki dizindeki dosyaları siler.

    Göreviniz öz yinelemeli şekilde bir klasördeki tüm HTML dosyalarını bulup onlarla bir zip dosyası oluşturmak. Unutmayın, komutunuz dosyalarınızın isminde boşluk olsa da çalışmalıdır. (ipucu: xargs‘ın -d işaretçisine bakın)

  3. (İleri Seviye) Özyinelemeli şekilde bir klasördeki en son düzenlenen dosyayı bulan bir komut veya script yazın. Daha genel olarak, tüm dosyaları en yeniden eskiye doğru sıralayabilir misiniz?


Bu sayfayı düzenle.

CC BY-NC-SA lisansı ile lisanslanmıştır.