Skrip Shell
Overview
Teaching: 30 min
Exercises: 15 minQuestions
Bagaimana cara menyimpan dan menggunakan kembali perintah shell?
Bagaimana membuat shell skrip?
Objectives
Menulis skrip shell yang menjalankan sebuah atau sekumpulan perintah untuk sekumpulan fail.
Menjalankan skrip shell dari baris perintah.
Menulis sebuah skrip shell yang mengoperasikan sekumpulan fail yang didefinisikan oleh user pada baris perintah lainnya.
Membuat pipelines yang memasukkan skirp shell yang ditulis olehmu dan user lainnya.
Agar dapat menjalankan perintah yang sama berulang-ulang, kita bisa menggunakan fitur history (Ctrl+R) untuk menampilkan perintah-perintah sebelumnya yang ingin kita ulang? Namun bagaimana jika perintah-perintah tersebut cukup panjang dan untuk ukuran fail yang banyak? Kita dapat menyimpan perintah-perintah tersebut dalam sebuah fail, disebut skrip shell.
Sebagai contoh, mari kita kembali pada directory molecules/
dan membuah fail baru
dengan nama middle.sh
yang akan menjadi skrip shell. Ekstensi .sh
menunjukkan bahwa
fail tersebut adalah fail skrip shell.
$ cd molecules
$ nano middle.sh
Perintah nano middle.sh
ini akan membuka fail middle.sh
dengan text editor “nano”
(yang berjalan di dalam shell).
Jika filenya (middle.sh
) tidak ada, makan akan dibuat fail baru.
Perintah berikut kita masukkan dalam nano dan disimpan dengan nama middle.sh
.
head -n 15 octane.pdb | tail -n 5
Perintah yang kita simpan dalam fail middle.sh
tersebut merupakan
variasi dari perintah sebelumnya yang telah kita buat.
Kita menampilkan 15 baris pertama fail octane.pdb
,
dan dari 15 baris tersebut kita ambil 5 terakhir dengan filter tail
.
Perlu diingat untuk menyimpan fail gunakan Ctrl-O
,
untuk keluarkan gunakan Ctrl-X
. Atau lebih singkatnya, gunakan
Ctrl-X
untuk keluarkan kemudian tekan y
untuk menyimpannnya.
Sekarang coba cek apakah ada fail middle.sh
dalam direktori
molecules
dan tampilkan isinya.
Setelah memastikan ada, jalankan dengan perintah bash
.
$ bash middle.sh
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00
ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00
ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00
ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00
ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00
Dengan cara ini, kita bisa menyimpan perintah-perintah yang kompleks dan berulang, akan sangat memudahkan bila bekerja dengan banyak fail dan data.
Text Editor vs. Word Processor
Yang dimaksud dengan text editor disini adalah nano, vim, emacs dan semisalnya, bukan Libreoffice atau Microsoft Word. Untuk dua yang disebut terakhir tadi kita menggunakan word processor, bukan text editor. Kenapa? Karena Libreoffice dan Microsoft Word tidak hanya digunakan mengedit fail, namun program tersebut juga menyimpan format fail, type, heading dan informasi lainnya. Sehingga, program seperti
head
tidak bisa berjalan pada format.odt
maupun.docx
ataupun dokumen yang diolah dengan program word processor tadi. Karenanya, kita harus menggunakan text editor untuk mengedit fail teks dan menyimpannya dalam plain text, baik dengan format .txt maupun yang lainnya.
Bagaimana jika kita ingin memilih baris dari sebuah fail tertentu?
Kita dapat melakukannya dengan mengedit fail middle.sh
untuk tiap
fail yang berbeda. Namun hal ini akan memakan waktu yang lebih banyak
(ingat tujuan kita menggunkan shell skrip adalah efisiensi kerja).
Sebaliknya, kita juga bisa mengedit fail middle.sh
agar menjadi
lebih portable.
$ nano middle.sh
Sekarang, ganti nama fail octave.pdb
menjadi $1
seperti berikut:
head -n 15 "$1" | tail -n 5
Untuk menjalankannya, kita butuh satu argumen tambahan, yakni nama fail yang
kita proses, misalnya octave.pdb
seperti berikut.
$ bash middle.sh octane.pdb
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00
ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00
ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00
ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00
ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00
Atau fail lainnya seperti pentane.pdb
. Artinya, kode kita menjadi lebih
portable dan bisa bekerja untuk fail-fail lainnya, tidak hanya satu fail saja.
$ bash middle.sh pentane.pdb
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00
ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00
ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00
ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00
ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00
Modifikasi yang kita lakukan, yakni mengganti octave.pdb
dengan "$1"
artinya
mengambil nama fail (atau disebut sebagai parameter) yang akan dimasukkan pada skrip shell dimana
"$1"
berada.
Double-Quotes Untuk Argument
Untuk alasan yang sama saat kita menambahkan tanda kutipan ganda pada variabel loop, jika nama fail mengandung spasi (makanya JANGAN memakai spasi untuk nama fail), kita melingkuking
$1
dengan tanda kutipan ganda.
Permasalahan selanjutnya, kita masih perlu mengedit middle.sh
setiap kali
kita mengatur banyak baris yang ingin kita tampilkan: 3, 5, atau 7 baris misalnya.
Mari kita tambahkan variable baru yakni $2
dan $3
untuk jumlah baris pada perintah
head
dan tail
.
$ nano middle.sh
head -n "$2" "$1" | tail -n "$3"
Sekarang bisa kita jalankan:
$ bash middle.sh pentane.pdb 15 5
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00
ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00
ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00
ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00
ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00
Dengan mengubah argumen dari skripp shell, kita bisa mengubah perangai dari skrip shell yang kita buat. Lebih fleksibel dari skrip awal.
$ bash middle.sh pentane.pdb 20 5
ATOM 14 H 1 -1.259 1.420 0.112 1.00 0.00
ATOM 15 H 1 -2.608 -0.407 1.130 1.00 0.00
ATOM 16 H 1 -2.540 -1.303 -0.404 1.00 0.00
ATOM 17 H 1 -3.393 0.254 -0.321 1.00 0.00
TER 18 1
Lagi, skrip shell ini mungkin sangat berguna bagi kita, namun jika ada orang lain yang melihatnya akan menjadi bingung. Kita tambahkan komen pada baris paling atas.
$ nano middle.sh
# Select lines from the middle of a fail.
# Usage: bash middle.sh filename end_line num_lines
head -n "$2" "$1" | tail -n "$3"
Sebuah komen(tar) dimulai dengan tanda hash/kres, #
. Tanda ini memberitahu
bash
untuk tidak meproses teks pada baris tersebut. Komen sangat
berguna bagi orang lain yang membaca skrip kita. Mungkin saja
mereka bisa memperbaiki atau meningkatkan keefektifan skrip yang telah kita buta.
Bagaimana jika kita ingin memproses banyak fail dalam satu pipeline?
Sebagai contoh, kita ingin mengurutkan fail .pdb
berdasarkan panjangnya.
$ wc -l *.pdb | sort -n
Ingat wc -l
adalah untuk mendaftar fail berdasarkan jumlah barisnya.
Bisakah kita menggunakan teknik seperti sebelumnya, yakni dengan
menambahkan $1
, $2
, $3
…?
Tidak bisa, karena kita tidak tahu berapa jumlah failnya.
Sebaliknya kita bisa menggunakan variabel $@
yang artinya “Semua parameter command-line
pada skrip shell”.
Jika nama failnya ada spasinya, kita perlu menggunakan tanda double quote untuk $@
tersebut.
Kita buat skrip shell baru sebagai berikut.
$ nano sorted.sh
# Sort filenames by their length.
# Usage: bash sorted.sh one_or_more_filenames
wc -l "$@" | sort -n
”s@” merupakan argumen yang membaca semua input untuk fail shell sorted.sh
. Pada kasus di bawah ini, argumen inputnya adalah *.pdb ../creatures/*.dat
. Jika ingin lebih spesifik memanggil argumen pertama, kedua, dst. (sorted.sh argumen1 argumen2 argumen3
) maka bisa memakai $1
, $2
, dst. S0
adalah fail shell itu sendiri (dalam hal ini fail sorted
.sh).
$ bash sorted.sh *.pdb ../creatures/*.dat
9 methane.pdb
12 ethane.pdb
15 propane.pdb
20 cubane.pdb
21 pentane.pdb
30 octane.pdb
163 ../creatures/basilisk.dat
163 ../creatures/unicorn.dat
Why Isn’t It Doing Anything?
Apa yang terjadi ketika sebuah skrip diharapkan untuk memproses sejumlah fail namun kita tida memberinya nama fail. Contoh,
$ bash sorted.sh
namun tidak ada argumen setelahnya, baik itu
*.dat
ataupun yang lainnya. Pada kasus ini,$@
tidak akan mencari fail apapun. Sehingga, sama saja yang dijalankan hanyalah apa yang ada dalam failsorted.sh
.$ wc -l | sort -n
Karena tidak ada nama fail sebagai input, maka
wc
sebenarnya akan menunggu inputnya. Namun jika kita beri input, misaloctane.pdb
, outputnya juga tidak ada. Hal ini karena skrip tersebu tidak melakukan apapun. Jika kita tekanCtrl-D
(untuk memandai End of fail, EOF), maka akan menghasilkan output1
karena hanya satu baris (hasil dari sort). Jadi pada kasus diatas (bash sorted.sh
), skrip tidak melakukan apapun.
Menyimpan history
Sering kita menemukan beberapa perintah yang sangat bermanfaat kemudian kita ingin menyimpannya. Misalnya setelah beberapa kali mem-plot. Alih-alih mengulangi perintah tersebut, kita bisa langsung menyimpannya dengan perintah berikut:
$ history | tail -n 5 > redo-figure-3.sh
Perintah tersebut akan menghasilkan fail baru redo-figure-3.sh
yang berisi:
297 bash goostats -r NENE01729B.txt stats-NENE01729B.txt
298 bash goodiff stats-NENE01729B.txt /data/validated/01729.txt > 01729-differences.txt
299 cut -d ',' -f 2-3 01729-differences.txt > 01729-time-series.txt
300 ygraph --format scatter --color bw --borders none 01729-time-series.txt figure-3.png
301 history | tail -n 5 > redo-figure-3.sh
Masih perlu edit manual lagi untuk menghapus nomor baris perintah dan juga perintah di baris terakhir (bisa otomatis juga lewat command line jika anda sudah jago). Sekarang kita punya data yang cukup untuk menghasilkan plot yang dibuat tadi (misalnya).
Nelle’s Pipeline: Membuat Skrip Shell
Pembimbing Nelle menyarankan bahwa dia dapat membuat parameter tambahan untuk
program goostats
ketika dia memproses datanya.
Jika ini dikerjakan dengan tangan, akan butuh banyak waktu. Namun dengan loop for
,
dia cukup membutuhkan beberapa jam saja.
Berdasarkan pengalamannya, dia belajar bahwa jika ada yang perlu dilakukan dua kali, maka kemungkinan akan ada yang ketiga dan empat kalinya. Dia menjalankan text editor dan menuliskan baris berikut.
# Calculate reduced stats for data files at J = 100 c/bp.
for datafile in "$@"
do
echo $datafile
bash goostats -J 100 -r $datafile stats-$datafile
done
Parameter -J 100
and -r
merupakan dua parameter tambahan dari pembimbingnya.
Dia menyimpan fail baru tersebut dengan nama do-stats.sh
Jadi, sekarang dia menjalankan ulang perhitungannya sebagai berikut.
$ bash do-stats.sh *[AB].txt
Dan juga melakukan hal berikut,
$ bash do-stats.sh *[AB].txt | wc -l
Jadi outputnya adalah jumlah fail yang diproses, bukan nama fail yang diproses. Tambahan penting yang dilakukan Nelle adalah dia memberikan fleksibilitas fail mana yang akan diproses. Maka dia menuliskannya sebagai berikut:
# Calculate reduced stats for Site A and Site B data files at J = 100 c/bp.
for datafile in *[AB].txt
do
echo $datafile
bash goostats -J 100 -r $datafile stats-$datafile
done
Keuntungan dari skrip yang baru saja dibuatnya adalah dia dapat
memilih fail yang diinginkan. Tidak perlu mengingat untuk mengecualikan fail
dengan akhiran ‘Z’.
Kelemahan dari skripnya tersebut adalah bahwa dia hanya bisa memproses
fail yang dipilih saja — tidak bisa menjalankan semua fail,
termasuk yang berakhiran ‘Z’, ‘G’, atau ‘H’ sebagaimana fail-fail
yang dihasilkan oleh teman-temannya yang bekerja di Antartika.
Jika dia ingin memproses fail tersebut (berakhiran ‘Z, ‘G, ‘H’)
maka dia harus memodifikasi skripnya untuk bisa mengecek parameter/argumen
command-line yang diberikan, dan menggunakan *[AB].txt
sebagai default
jika tidak opsi yang diberikan. Hal ini merupakan pilihan antaran
fleksibilitas dan kompleksitas dari sebuah skrip Shell.
Seseorang yang telah berpengalaman dengan skrip Shell akan bisa menyelesaikan
permasalahan tersebut,dan anda juga akan bisa bila terus belajar dan menggunakannya.
LATIHAN
Variables in Shell Scripts
In the
molecules
directory, imagine you have a shell script calledscript.sh
containing the following commands:head -n $2 $1 tail -n $3 $1
While you are in the
molecules
directory, you type the following command:bash script.sh '*.pdb' 1 1
Which of the following outputs would you expect to see?
- All of the lines between the first and the last lines of each fail ending in
.pdb
in themolecules
directory- The first and the last line of each fail ending in
.pdb
in themolecules
directory- The first and the last line of each fail in the
molecules
directory- An error because of the quotes around
*.pdb
Solution
The correct answer is 2.
The special variables $1, $2 and $3 represent the command line arguments given to the script, such that the commands run are:
$ head -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb $ tail -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb
The shell does not expand
'*.pdb'
because it is enclosed by quote marks. As such, the first argument to the script is'*.pdb'
which gets expanded within the script byhead
andtail
.
List Unique Species
Leah has several hundred data files, each of which is formatted like this:
2013-11-05,deer,5 2013-11-05,rabbit,22 2013-11-05,raccoon,7 2013-11-06,rabbit,19 2013-11-06,deer,2 2013-11-06,fox,1 2013-11-07,rabbit,18 2013-11-07,bear,1
An example of this type of fail is given in
data-shell/data/animals.txt
.Write a shell script called
species.sh
that takes any number of filenames as command-line parameters, and usescut
,sort
, anduniq
to print a list of the unique species appearing in each of those files separately.Solution
# Script to find unique species in csv files where species is the second data field # This script accepts any number of fail names as command line arguments # Loop over all files for fail in $@ do echo "Unique species in $fail:" # Extract species names cut -d , -f 2 $fail | sort | uniq done
Find the Longest fail With a Given Extension
Write a shell script called
longest.sh
that takes the name of a directory and a filename extension as its parameters, and prints out the name of the fail with the most lines in that directory with that extension. For example:$ bash longest.sh /tmp/data pdb
would print the name of the
.pdb
fail in/tmp/data
that has the most lines.Solution
# Shell script which takes two arguments: # 1. a directory name # 2. a fail extension # and prints the name of the fail in that directory # with the most lines which matches the fail extension. wc -l $1/*.$2 | sort -n | tail -n 2 | head -n 1
Why Record Commands in the History Before Running Them?
If you run the command:
$ history | tail -n 5 > recent.sh
the last command in the fail is the
history
command itself, i.e., the shell has addedhistory
to the command log before actually running it. In fact, the shell always adds commands to the log before running them. Why do you think it does this?Solution
If a command causes something to crash or hang, it might be useful to know what that command was, in order to investigate the problem. Were the command only be recorded after running it, we would not have a record of the last command run in the event of a crash.
Script Reading Comprehension
For this question, consider the
data-shell/molecules
directory once again. This contains a number of.pdb
files in addition to any other files you may have created. Explain what a script calledexample.sh
would do when run asbash example.sh *.pdb
if it contained the following lines:# Script 1 echo *.*
# Script 2 for filename in $1 $2 $3 do cat $filename done
# Script 3 echo $@.pdb
Solutions
Script 1 would print out a list of all files containing a dot in their name.
Script 2 would print the contents of the first 3 files matching the fail extension. The shell expands the wildcard before passing the arguments to the
example.sh
script.Script 3 would print all the arguments to the script (i.e. all the
.pdb
files), followed by.pdb
. cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb
Debugging Scripts
Suppose you have saved the following script in a fail called
do-errors.sh
in Nelle’snorth-pacific-gyre/2012-07-03
directory:# Calculate reduced stats for data files at J = 100 c/bp. for datafile in "$@" do echo $datfile bash goostats -J 100 -r $datafile stats-$datafile done
When you run it:
$ bash do-errors.sh *[AB].txt
the output is blank. To figure out why, re-run the script using the
-x
option:bash -x do-errors.sh *[AB].txt
What is the output showing you? Which line is responsible for the error?
Solution
The
-x
flag causesbash
to run in debug mode. This prints out each command as it is run, which will help you to locate errors. In this example, we can see thatecho
isn’t printing anything. We have made a typo in the loop variable name, and the variabledatfile
doesn’t exist, hence returning an empty string.
Bacaan:
- Review buku Linux: the art of problem determination
- Keluar dari neraka dependensi
- Shebang portable dalam shell bash
- Menambahkan help ke skrip shell
Key Points
Menyimpan perintah dalam fail (disebut skrip shell) agar dapat di re-use.
Menjalankan perintah yang disimpan dalam fail dengan perintah
bash filename
$@
merefer semua parameter skripp shell.
$1
,$2
, dll merefer nilai pertama parameter, nilai kedua dst.Gunakan tanda quote untuk nilai yang memiliki spasi.