Panduan Gaya Shell Google (dalam bahasa Rusia)

Kata Pengantar


Shell yang digunakan


Bash satu Bash satunya bahasa skrip shell yang dapat digunakan untuk file yang dapat dieksekusi.


Skrip harus dimulai dengan #!/bin/bash dengan set minimal flag. Gunakan set untuk mengatur opsi shell sehingga memanggil skrip Anda sebagai bash <script_name> tidak melanggar fungsinya.


Membatasi semua skrip shell menjadi bash memberi kami bahasa shell yang konsisten yang diinstal pada semua mesin kami.


Satu-satunya pengecualian adalah jika Anda dibatasi oleh kondisi pemrograman Anda. Salah satu contohnya adalah paket Solaris SVR4, yang membutuhkan penggunaan shell Bourne biasa untuk skrip apa pun.


Kapan harus menggunakan Shell


Shell harus digunakan hanya untuk utilitas kecil atau pembungkus skrip sederhana.


Meskipun shell scripting bukan bahasa pengembangan, ini digunakan untuk menulis berbagai utilitas di seluruh Google. Panduan gaya ini lebih merupakan pengakuan atas penggunaannya, daripada proposal untuk menggunakannya dalam penggunaan luas.


Beberapa rekomendasi:


  • Jika Anda paling sering memanggil utilitas lain dan melakukan sedikit manipulasi data, shell adalah pilihan yang dapat diterima untuk tugas tersebut.
  • Jika masalah kinerja, gunakan sesuatu yang lain tetapi tidak shell.
  • Jika Anda perlu menggunakan array lebih dari menugaskan ${PIPESTATUS} , Anda harus menggunakan Python.
  • Jika Anda menulis skrip yang lebih panjang dari 100 baris, Anda mungkin harus menulisnya dengan Python. Ingatlah bahwa skrip berkembang. Tulis ulang skrip Anda dalam bahasa lain sebelumnya untuk menghindari penulisan ulang yang memakan waktu nanti.

File shell dan panggilan juru bahasa


Ekstensi file


File yang dapat dieksekusi tidak boleh memiliki ekstensi (sangat disukai) atau ekstensi .sh . Perpustakaan harus memiliki ekstensi .sh dan tidak boleh dieksekusi.


Tidak perlu tahu bahasa apa yang ditulis oleh program selama eksekusi, dan shell tidak memerlukan ekstensi, jadi kami lebih memilih untuk tidak menggunakannya untuk file yang dapat dieksekusi.


Namun, penting bagi perpustakaan untuk mengetahui bahasa apa itu ditulis, dan kadang-kadang perlu untuk memiliki perpustakaan serupa dalam bahasa yang berbeda. Ini memungkinkan Anda memiliki file perpustakaan yang dinamai secara identik dengan tujuan yang identik, tetapi ditulis dalam bahasa yang berbeda harus identik dalam namanya, kecuali untuk sufiks khusus bahasa.


SUID / SGID


SUID dan SGID dilarang pada skrip shell.


Ada terlalu banyak masalah keamanan, sehingga hampir tidak mungkin untuk menyediakan perlindungan SUID / SGID yang cukup. Meskipun bash mempersulit peluncuran SUID, masih dimungkinkan pada beberapa platform, jadi kami secara eksplisit melarang penggunaannya.


Gunakan sudo untuk meningkatkan akses jika Anda membutuhkannya.


Lingkungan


STDOUT vs STDERR


Semua pesan kesalahan harus dikirim ke STDERR .


Ini membantu memisahkan keadaan normal dari masalah aktual.


Fungsi untuk menampilkan pesan kesalahan direkomendasikan untuk digunakan bersama dengan informasi status lainnya.


 err() { echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2 } if ! do_something; then err "Unable to do_something" exit "${E_DID_NOTHING}" fi 

Komentar


Header file


Mulai setiap file dengan deskripsi kontennya.


Setiap file harus memiliki judul dari komentar, termasuk deskripsi singkat tentang isinya. Pemberitahuan hak cipta dan informasi penulis adalah opsional.


Contoh:


 #!/bin/bash # # Perform hot backups of Oracle databases. 

Komentar Fitur


Setiap fungsi yang tidak jelas dan singkat harus dikomentari. Setiap fungsi di perpustakaan harus dikomentari terlepas dari panjang atau kompleksitasnya.


Anda perlu memastikan bahwa orang lain memahami cara menggunakan program Anda atau cara menggunakan fungsi di perpustakaan Anda hanya dengan membaca komentar (dan kebutuhan untuk perbaikan diri) tanpa membaca kode.


Semua komentar fitur harus mencakup:


  • Deskripsi Fungsi
  • Variabel global yang digunakan dan dimodifikasi
  • Argumen diterima
  • Mengembalikan nilai yang berbeda dari kode keluar standar di perintah terakhir.

Contoh:


 #!/bin/bash # # Perform hot backups of Oracle databases. export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin' ######################################## # Cleanup files from the backup dir # Globals: # BACKUP_DIR # ORACLE_SID # Arguments: # None # Returns: # None ######################################## cleanup() { ... } 

Komentar Implementasi


Komentari bagian kode Anda yang kompleks, tidak jelas, menarik, atau penting.


Ini diasumsikan sebagai praktik umum dari kode komentar di Google. Jangan mengomentari semuanya. Jika ada algoritma yang rumit atau Anda melakukan sesuatu yang tidak biasa, tambahkan komentar singkat.


Komentar TODO


Gunakan komentar TODO untuk kode yang bersifat sementara, jangka pendek, atau cukup bagus, tetapi tidak sempurna.


Ini konsisten dengan konvensi dalam manual C ++ .


Komentar TODO harus menyertakan kata TODO dalam huruf kapital, diikuti dengan nama Anda dalam tanda kurung. Tanda titik dua adalah opsional. Lebih disukai untuk menunjukkan nomor bug / tiket di sebelah elemen TODO.


Contoh:


 # TODO(mrmonkey): Handle the unlikely edge cases (bug ####) 

Memformat


Meskipun Anda harus mengikuti gaya yang sudah digunakan dalam file yang Anda edit, berikut ini diperlukan untuk kode baru apa pun.


Lekukan


Indentasi 2 spasi. Tidak ada tab.


Gunakan garis kosong di antara blok untuk meningkatkan keterbacaan. Lekukan adalah dua ruang. Apa pun yang Anda lakukan, jangan gunakan tab. Untuk file yang ada, tetap setia pada lekukan saat ini.


Panjang String dan Nilai Panjang


Panjang garis maksimum adalah 80 karakter.


Jika Anda perlu menulis baris yang lebih panjang dari 80 karakter, ini harus dilakukan menggunakan here document atau, jika mungkin, newline . Nilai literal yang bisa lebih dari 80 karakter dan tidak dapat dipisahkan diperbolehkan secara wajar, tetapi sangat disarankan agar Anda menemukan cara untuk membuatnya lebih pendek.


 #  'here document's cat <<END; I am an exceptionally long string. END #  newlines   long_string="I am an exceptionally long string." 

Jaringan pipa


Pipa harus dibagi masing-masing pada satu baris jika tidak cocok pada satu saluran.


Jika pipa cocok pada satu saluran, itu harus di satu saluran.


Jika tidak, itu harus dibagi sehingga setiap bagian berada pada baris baru dan di-indentasi oleh 2 spasi untuk bagian selanjutnya. Ini merujuk ke rantai perintah yang dikombinasikan menggunakan '|' serta koneksi logis menggunakan '||' dan '&&'.


 #      command1 | command2 #   command1 \ | command2 \ | command3 \ | command4 

Siklus


Tempat ; do ; do dan ; then ; then pada baris yang sama dengan while , for atau if .


Siklus dalam shell sedikit berbeda, tetapi kami mengikuti prinsip yang sama seperti dengan kurung kurawal saat mendeklarasikan fungsi. Yaitu ; then ; then dan ; do ; do harus pada baris yang sama seperti if / for / while . else harus di jalur yang terpisah, dan pernyataan penutup harus di jalur mereka sendiri, selaras secara vertikal dengan pernyataan pembukaan.


Contoh:


 for dir in ${dirs_to_cleanup}; do if [[ -d "${dir}/${ORACLE_SID}" ]]; then log_date "Cleaning up old files in ${dir}/${ORACLE_SID}" rm "${dir}/${ORACLE_SID}/"* if [[ "$?" -ne 0 ]]; then error_message fi else mkdir -p "${dir}/${ORACLE_SID}" if [[ "$?" -ne 0 ]]; then error_message fi fi done 

Pernyataan kasus


  • Pisahkan opsi dalam 2 spasi.
  • Opsi baris tunggal memerlukan spasi setelah braket penutup template dan sebelum ;; .
  • Opsi panjang atau multi-perintah harus dibagi menjadi beberapa baris dengan templat, tindakan, dan ;; pada baris terpisah.

Ekspresi yang sesuai surut satu tingkat dari case dan esac . Tindakan multiline juga memiliki indentasi pada tingkat yang terpisah. Tidak perlu menempatkan ekspresi dalam tanda kutip. Pola ekspresi tidak boleh didahului oleh tanda kurung terbuka. Hindari menggunakan &; dan ;;& notasi.


 case "${expression}" in a) variable="..." some_command "${variable}" "${other_expr}" ... ;; absolute) actions="relative" another_command "${actions}" "${other_expr}" ... ;; *) error "Unexpected expression '${expression}'" ;; esac 

Perintah sederhana dapat ditempatkan pada satu baris dengan pola dan ;; sementara ekspresi tetap dapat dibaca. Ini sering cocok untuk menangani opsi huruf tunggal. Ketika tindakan tidak sesuai pada satu baris, biarkan templat di baris Anda, tindakan selanjutnya, lalu ;; juga sejalan. Ketika ini adalah baris yang sama dengan tindakan, gunakan spasi setelah kurung tutup template dan yang lain sebelum ;; .


 verbose='false' aflag='' bflag='' files='' while getopts 'abf:v' flag; do case "${flag}" in a) aflag='true' ;; b) bflag='true' ;; f) files="${OPTARG}" ;; v) verbose='true' ;; *) error "Unexpected option ${flag}" ;; esac done 

Ekspansi variabel


Dalam urutan prioritas: amati apa yang sudah digunakan; lampirkan variabel dalam tanda kutip; lebih suka "${var}" lebih dari "$var" , tetapi dengan memperhatikan konteks penggunaan.


Ini lebih merupakan rekomendasi, karena topik ini cukup kontroversial untuk regulasi wajib. Mereka terdaftar dalam urutan prioritas.


  1. Gunakan gaya yang sama yang Anda temukan dalam kode yang ada.
  2. Masukkan variabel dalam tanda kutip, lihat bagian Kutipan di bawah ini.
  3. Jangan letakkan karakter tunggal khusus untuk parameter shell / posisi di tanda kutip dan kawat gigi kecuali benar-benar diperlukan dan untuk menghindari kebingungan yang mendalam.
    Pilih kurung kurawal untuk semua variabel lainnya.


      #    #    '' : echo "Positional: $1" "$5" "$3" echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..." #   : echo "many parameters: ${10}" #    : # Output is "a0b0c0" set -- abc echo "${1}0${2}0${3}0" #     : echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}" while read f; do echo "file=${f}" done < <(ls -l /tmp) #    #   ,    , #     ,   shell echo a=$avar "b=$bvar" "PID=${$}" "${1}" #  : #    "${1}0${2}0${3}0",   "${10}${20}${30} set -- abc echo "$10$20$30" 

    Kutipan


    • Selalu gunakan tanda kutip untuk nilai-nilai yang mengandung variabel, penggantian perintah, spasi atau karakter meta shell, sampai Anda perlu dengan aman mengekspos nilai-nilai tidak dalam tanda kutip.
    • Lebih suka tanda kutip untuk nilai-nilai yang merupakan "kata-kata" (yang bertentangan dengan parameter perintah atau nama jalur)
    • Jangan pernah mengutip bilangan bulat.
    • Ketahui bagaimana tanda kutip bekerja untuk pola kecocokan di [[ .
    • Gunakan "$@" jika Anda tidak memiliki alasan khusus untuk menggunakan $* .


 # ''  ,     . # ""  ,   /. #   # "   " flag="$(some_command and its args "$@" 'quoted separately')" # "  " echo "${flag}" # "      " value=32 # "    ",      number="$(generate_number)" # "   ",    readonly USE_INTEGER='true' # "    - shell" echo 'Hello stranger, and well met. Earn lots of $$$' echo "Process $$: Done making \$\$\$." # "    " # ( ,  $1  ) grep -li Hugo /dev/null "$1" #    # "   ,    ": ccs     git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"} #   : $1    #       . grep -cP '([Ss]pecial|\|?characters*)$' ${1:+"$1"} #   , # "$@"   ,  # $*    # # * $*  $@   ,   #      ; # * "$@"     ,   #       ; #     ,      #   # * "$*"    ,    #     () , #        # ( 'man bash'  nit-grits ;-) set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$*"; echo "$#, $@") set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$@"; echo "$#, $@") 

Fitur dan Kesalahan


Substitusi perintah


Gunakan $(command) daripada backtick.


Backticks bersarang membutuhkan pelolosan internal dengan \ . Format $ (command) tidak berubah tergantung pada sarangnya dan lebih mudah dibaca.


Contoh:


 #  : var="$(command "$(command1)")" #  : var="`command \`command1\``" 

Cek, [ dan [[


[[ ... ]] lebih disukai daripada [ , test atau /usr/bin/[ .


[[ ... ]] mengurangi kemungkinan kesalahan, karena tidak ada resolusi jalur atau pemisahan kata antara [[ dan ]] , dan [[ ... ]] memungkinkan Anda untuk menggunakan ekspresi reguler di mana [ ... ] tidak.


 #  ,       #  `alnum`,     . #  ,       #  .  ,  # E14   https://tiswww.case.edu/php/chet/bash/FAQ if [[ "filename" =~ ^[[:alnum:]]+name ]]; then echo "Match" fi #     "f*" (    ) if [[ "filename" == "f*" ]]; then echo "Match" fi #    "too many arguments",   f*   #     if [ "filename" == f* ]; then echo "Match" fi 

Periksa Nilai


Gunakan tanda kutip daripada karakter tambahan jika memungkinkan.


Bash cukup pintar untuk bekerja dengan string kosong dalam ujian. Oleh karena itu, kode yang dihasilkan lebih mudah dibaca, gunakan cek untuk nilai kosong / tidak kosong atau nilai kosong, tanpa menggunakan karakter tambahan.


 #  : if [[ "${my_var}" = "some_string" ]]; then do_something fi # -z (   ),  -n (    ): #      if [[ -z "${my_var}" ]]; then do_something fi #   ( ),   : if [[ "${my_var}" = "" ]]; then do_something fi #   : if [[ "${my_var}X" = "some_stringX" ]]; then do_something fi 

Untuk menghindari kebingungan tentang apa yang Anda periksa, gunakan secara eksplisit -z atau -n .


 #   if [[ -n "${my_var}" ]]; then do_something fi #  ,    ,  ${my_var} #     . if [[ "${my_var}" ]]; then do_something fi 

Ekspresi Substitusi untuk Nama File


Gunakan jalur eksplisit saat membuat ekspresi wildcard untuk nama file.


Karena nama file dapat dimulai dengan - karakter, jauh lebih aman untuk ./* ekspresi wildcard sebagai ./* daripada * .


 #   : # -f -r somedir somefile #        force psa@bilby$ rm -v * removed directory: `somedir' removed `somefile' #   : psa@bilby$ rm -v ./* removed `./-f' removed `./-r' rm: cannot remove `./somedir': Is a directory removed `./somefile' 

Eval


eval harus dihindari.


Eval memungkinkan Anda untuk memperluas variabel yang diteruskan dalam input, tetapi juga dapat mengatur variabel lain, tanpa kemungkinan memeriksanya.


 #   ? #   ?   ? eval $(set_my_variables) #  ,         ? variable="$(eval some_function)" 

Pipes in While


Gunakan substitusi perintah atau for loop, daripada pipa while . Variabel yang diubah di while tidak merambat ke induk, karena perintah loop dijalankan di sub-shell.


Sub-shell implisit dalam pipa while dapat membuat pelacakan kesalahan sulit.


 last_line='NULL' your_command | while read line; do last_line="${line}" done #   'NULL' echo "${last_line}" 

Gunakan for for loop jika Anda yakin bahwa input tidak akan mengandung spasi atau karakter khusus (biasanya ini tidak menyiratkan input pengguna).


 total=0 #  ,       . for value in $(command); do total+="${value}" done 

Menggunakan substitusi perintah memungkinkan Anda untuk mengarahkan ulang output, tetapi mengeksekusi perintah dalam sub-shell eksplisit, tidak seperti sub-shell implisit, yang menciptakan bash untuk while .


 total=0 last_file= while read count filename; do total+="${count}" last_file="${filename}" done < <(your_command | uniq -c) #         # . echo "Total = ${total}" echo "Last one = ${last_file}" 

Gunakan while loop di mana tidak perlu memberikan hasil yang kompleks ke shell induk - ini adalah tipikal ketika diperlukan "parsing" yang lebih kompleks. Ingatlah bahwa contoh sederhana kadang-kadang jauh lebih mudah untuk diselesaikan dengan menggunakan alat seperti awk. Ini juga dapat berguna ketika Anda secara khusus tidak ingin mengubah variabel lingkungan induk.


 #    awk: # awk '$3 == "nfs" { print $2 " maps to " $1 }' /proc/mounts cat /proc/mounts | while read src dest type opts rest; do if [[ ${type} == "nfs" ]]; then echo "NFS ${dest} maps to ${src}" fi done 

Konvensi penamaan


Nama Fungsi


Huruf kecil dengan garis bawah untuk kata-kata yang terpisah. Pisahkan perpustakaan dengan :: . Kurung diperlukan setelah nama fungsi. Kata kunci fungsi adalah opsional, tetapi jika digunakan, konsisten di seluruh proyek.


Jika Anda menulis fungsi yang terpisah, gunakan huruf kecil dan kata yang terpisah dengan garis bawah. Jika Anda menulis paket, pisahkan nama paket dengan :: . Tanda kurung harus pada baris yang sama dengan nama fungsi (seperti dalam bahasa lain di Google), dan tidak memiliki spasi antara nama fungsi dan braket.


 #   my_func() { ... } #   mypackage::my_func() { ... } 

Ketika "()" muncul setelah nama fungsi, maka kata kunci fungsi terlihat berlebihan, tetapi meningkatkan identifikasi fungsi dengan cepat.


Nama Variabel


Mengenai nama fungsi.


Nama variabel untuk loop harus sama dengan nama untuk variabel apa pun yang Anda ikuti.


 for zone in ${zones}; do something_with "${zone}" done 

Nama konstan variabel lingkungan


Semua dalam huruf kapital, dipisahkan oleh garis bawah, dideklarasikan di bagian atas file.


Konstanta dan segala sesuatu yang diekspor ke lingkungan harus dalam huruf besar.


 #  readonly PATH_TO_FILES='/some/path' # ,   declare -xr ORACLE_SID='PROD' 

Beberapa hal tetap konstan saat pertama kali diinstal (misalnya, melalui getopts ). Dengan demikian, sangat normal untuk menetapkan konstanta melalui getopts atau berdasarkan kondisi, tetapi harus dilakukan hanya setelah itu. Perhatikan bahwa declare tidak berfungsi dengan variabel global di dalam fungsi, jadi sebaiknya readonly atau export .


 VERBOSE='false' while getopts 'v' flag; do case "${flag}" in v) VERBOSE='true' ;; esac done readonly VERBOSE 

Sumber Nama File


Huruf kecil, dengan garis bawah untuk memisahkan kata, jika perlu.


Ini berlaku untuk mencocokkan gaya kode lainnya di Google: maketemplate atau make_template , tetapi tidak make-template .


Variabel Hanya Baca


Gunakan hanya readonly atau declare -r untuk memastikan itu hanya-baca.


shell, . , , .


 zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)" if [[ -z "${zip_version}" ]]; then error_message else readonly zip_version fi 


, , local . .


, , local . , .


, ; local exit code .


 my_func2() { local name="$1" #      : local my_var my_var="$(my_func)" || return #   : $?  exit code  'local',   my_func local my_var="$(my_func)" [[ $? -eq 0 ]] || return ... } 


. .


, . , set , .


. .


main


, main , , .


, main . , ( , ). main:


 main "$@" 

, , , main — , .




.


$? if , .


:


 if ! mv "${file_list}" "${dest_dir}/" ; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi #  mv "${file_list}" "${dest_dir}/" if [[ "$?" -ne 0 ]]; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi Bash    `PIPESTATUS`,         .           ,   : ```bash tar -cf - ./* | ( cd "${dir}" && tar -xf - ) if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then echo "Unable to tar files to ${dir}" >&2 fi 

, PIPESTATUS , - , , PIPESTATUS ( , [ PIPESTATUS ).


 tar -cf - ./* | ( cd "${DIR}" && tar -xf - ) return_codes=(${PIPESTATUS[*]}) if [[ "${return_codes[0]}" -ne 0 ]]; then do_something fi if [[ "${return_codes[1]}" -ne 0 ]]; then do_something_else fi 


shell , .


, bash, ( , sed ).


:


 #  : addition=$((${X} + ${Y})) substitution="${string/#foo/bar}" #  : addition="$(expr ${X} + ${Y})" substitution="$(echo "${string}" | sed -e 's/^foo/bar/')" 

Kesimpulan


.


Silakan luangkan beberapa menit untuk membaca bagian Kata Berpisah di bagian bawah manual C ++ .

Source: https://habr.com/ru/post/id413155/


All Articles