Apa tujuan dari: (titik dua) GNU Bash builtin?


336

Apa tujuan dari suatu perintah yang tidak melakukan apa-apa, hanya sebagai pemimpin komentar, tetapi sebenarnya adalah sebuah shell yang dibangun di dalam dan dari dirinya sendiri?

Ini lebih lambat daripada memasukkan komentar ke dalam skrip Anda sekitar 40% per panggilan, yang mungkin sangat bervariasi tergantung pada ukuran komentar. Satu-satunya alasan yang mungkin bisa saya lihat adalah:

# poor man's delay function
for ((x=0;x<100000;++x)) ; do : ; done

# inserting comments into string of commands
command ; command ; : we need a comment in here for some reason ; command

# an alias for `true' (lazy programming)
while : ; do command ; done

Saya kira apa yang sebenarnya saya cari adalah aplikasi historis apa yang mungkin dimilikinya.



69
@ Caleb - Saya menanyakan ini dua tahun sebelumnya.
amphetamachine

Saya tidak akan mengatakan perintah yang mengembalikan nilai tertentu "tidak melakukan apa-apa." Kecuali pemrograman fungsional terdiri dari "tidak melakukan apa-apa." :-)
LarsH

Jawaban:


415

Secara historis , cangkang Bourne tidak memiliki truedan falsesebagai perintah bawaan. truebukan hanya alias :, dan falseuntuk sesuatu seperti let 0.

:sedikit lebih baik daripada trueuntuk portabilitas ke shell yang diturunkan dari Bourne kuno. Sebagai contoh sederhana, pertimbangkan untuk tidak memiliki !operator pipa atau ||operator daftar (seperti halnya beberapa cangkang Bourne kuno). Ini meninggalkan elseklausa ifpernyataan sebagai satu-satunya cara untuk bercabang berdasarkan status keluar:

if command; then :; else ...; fi

Karena ifmemerlukan thenklausa non-kosong dan komentar tidak dihitung sebagai non-kosong, :berfungsi sebagai larangan.

Saat ini (yaitu: dalam konteks modern) Anda biasanya dapat menggunakan salah satu :atau true. Keduanya ditentukan oleh POSIX, dan beberapa menemukan truelebih mudah dibaca. Namun ada satu perbedaan yang menarik: :adalah yang disebut POSIX khusus built-in , sedangkan trueadalah biasa built-in .

  • Dibangun khusus harus dibangun ke dalam shell; Built-in biasa hanya "biasanya" built-in, tetapi tidak dijamin sepenuhnya. Seharusnya tidak ada program reguler yang dinamai :dengan fungsi truedi PATH dari kebanyakan sistem.

  • Mungkin perbedaan yang paling penting adalah bahwa dengan built-in khusus, variabel apa pun yang ditetapkan oleh built-in - bahkan di lingkungan selama evaluasi perintah sederhana - tetap ada setelah perintah selesai, seperti yang ditunjukkan di sini menggunakan ksh93:

    $ unset x; ( x=hi :; echo "$x" )
    hi
    $ ( x=hi true; echo "$x" )
    
    $

    Perhatikan bahwa Zsh mengabaikan persyaratan ini, seperti halnya GNU Bash kecuali ketika beroperasi dalam mode kompatibilitas POSIX, tetapi semua shell "POSIX sh turunan" utama lainnya mengamati ini termasuk dash, ksh93, dan mksh.

  • Perbedaan lainnya adalah built-in reguler harus kompatibel dengan exec- ditunjukkan di sini menggunakan Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX juga secara eksplisit mencatat bahwa :mungkin lebih cepat daripada true, meskipun ini tentu saja detail implementasi khusus.


Apakah maksud Anda built-in biasa tidak boleh kompatibel exec?
Old Pro

1
@ OldPro: Tidak, dia benar karena itu trueadalah builtin biasa, tapi dia salah dalam execmenggunakan /bin/truebukannya builtin.
Dijeda sampai pemberitahuan lebih lanjut.

1
@ DennisWilliamson Saya hanya akan dengan cara spec yang diucapkan. Implikasinya tentu saja bahwa builtin reguler juga harus memiliki versi mandiri.
ormaaj

17
+1 Jawaban luar biasa. Saya masih ingin mencatat penggunaan untuk menginisialisasi variabel, seperti : ${var?not initialized}et al.
tripleee

Tindak lanjut yang lebih atau kurang terkait. Anda mengatakan :adalah built-in khusus dan seharusnya tidak memiliki fungsi yang dinamai olehnya. Tapi bukankah contoh yang paling umum terlihat dari fork bomb :(){ :|: & };:penamaan fungsi dengan nama :?
Chong

63

Saya menggunakannya untuk dengan mudah mengaktifkan / menonaktifkan perintah variabel:

#!/bin/bash
if [[ "$VERBOSE" == "" || "$VERBOSE" == "0" ]]; then
    vecho=":"     # no "verbose echo"
else
    vecho=echo    # enable "verbose echo"
fi

$vecho "Verbose echo is ON"

Jadi

$ ./vecho
$ VERBOSE=1 ./vecho
Verbose echo is ON

Ini membuat skrip bersih. Ini tidak dapat dilakukan dengan '#'.

Juga,

: >afile

adalah salah satu cara paling sederhana untuk menjamin bahwa 'afile' ada tetapi panjangnya 0.


20
>afilebahkan lebih sederhana dan mencapai efek yang sama.
earl

2
Keren, saya akan menggunakan trik $ vecho untuk menyederhanakan skrip yang saya kelola.
BarneySchmale

5
Apa untungnya mengutip tanda titik dua vecho=":"? Hanya untuk keterbacaan?
leoj

56

Aplikasi yang berguna untuk: adalah jika Anda hanya tertarik menggunakan ekspansi parameter untuk efek sampingnya daripada benar-benar meneruskan hasilnya ke perintah. Dalam hal ini Anda menggunakan PE sebagai argumen untuk: atau salah tergantung pada apakah Anda ingin status keluar dari 0 atau 1. Contoh mungkin : "${var:=$1}". Karena :builtin itu harus cukup cepat.


2
Anda juga dapat menggunakannya untuk efek samping dari ekspansi aritmatika: : $((a += 1))( ++dan --operator tidak perlu diimplementasikan sesuai dengan POSIX.). Dalam bash, ksh, dan kemungkinan shell lain yang dapat Anda lakukan juga: ((a += 1))atau ((a++))tetapi tidak ditentukan oleh POSIX.
pabouk

@ poabuk Yap itu semua benar, meskipun (())ditentukan sebagai fitur opsional. "Jika urutan karakter yang dimulai dengan" (("akan diuraikan oleh shell sebagai ekspansi aritmatika jika didahului oleh '$', shell yang menerapkan ekstensi dengan mana" ((ekspresi)) "dievaluasi sebagai ekspresi aritmatika dapat memperlakukan "((" sebagai pengantar evaluasi aritmatika alih-alih perintah pengelompokan. "
ormaaj

50

:bisa juga untuk komentar blok (mirip dengan / * * / dalam bahasa C). Misalnya, jika Anda ingin melewatkan satu blok kode dalam skrip Anda, Anda bisa melakukan ini:

: << 'SKIP'

your code block here

SKIP

3
Ide buruk. Penggantian perintah apa pun di dalam dokumen di sini masih diproses.
chepner

33
Bukan ide yang buruk. Anda dapat menghindari resolusi / penggantian variabel di sini dokumen dengan mengutip tanda pembatas tunggal:: << 'SKIP'
Rondo

1
IIRC Anda juga dapat \ melarikan diri salah satu karakter pembatas untuk efek yang sama: : <<\SKIP.
yyny

@zagpoint Apakah ini tempat Python menggunakan docstring sebagai komentar multiline?
Sapphire_Brick

31

Jika Anda ingin memotong file ke nol byte, berguna untuk menghapus log, coba ini:

:> file.log

16
> file.loglebih sederhana dan mencapai efek yang sama.
amphetamachine

55
Yah, tapi wajah bahagia itu untukku:>
Ahi Tuna

23
@amphetamachine: :>lebih portabel. Beberapa cangkang (seperti saya zsh) secara otomatis-instantiate kucing di cangkang saat ini dan dengarkan stdin ketika diberi arahan ulang tanpa perintah. Alih-alih cat /dev/null, :jauh lebih sederhana. Seringkali perilaku ini berbeda di shell interaktif daripada skrip, tetapi jika Anda menulis skrip dengan cara yang juga berfungsi interaktif, debug dengan copy-paste jauh lebih mudah.
Caleb

2
Bagaimana : > fileperbedaan dari true > file(selain dari jumlah karakter dan wajah bahagia) di shell modern (dengan asumsi :dan truesama-sama cepat)?
Adam Katz


29

Dua kegunaan lain yang tidak disebutkan dalam jawaban lain:

Penebangan

Ambil contoh skrip ini:

set -x
: Logging message here
example_command

Baris pertama set -x,, membuat shell mencetak perintah sebelum menjalankannya. Ini konstruksi yang cukup berguna. Kelemahannya adalah bahwa echo Log messagejenis pernyataan yang biasa sekarang mencetak pesan dua kali. Metode usus besar mengatasinya. Perhatikan bahwa Anda masih harus keluar dari karakter khusus seperti yang Anda inginkan echo.

Judul pekerjaan cron

Saya telah melihatnya digunakan dalam pekerjaan cron, seperti ini:

45 10 * * * : Backup for database ; /opt/backup.sh

Ini adalah tugas cron yang menjalankan skrip /opt/backup.shsetiap hari pada pukul 10:45. Keuntungan dari teknik ini adalah membuat subjek email menjadi lebih baik ketika /opt/backup.shmencetak beberapa output.


Di mana lokasi log default? Bisakah saya mengatur lokasi log? Apakah tujuannya lebih untuk menciptakan keluaran di stdout selama proses skrip / latar belakang?
domdambrogia

1
@domdambrogia Saat menggunakan set -x, perintah yang dicetak (termasuk sesuatu seperti : foobar) pergi ke stderr.
Flimm

23

Anda bisa menggunakannya bersamaan dengan backticks ( ``) untuk menjalankan perintah tanpa menampilkan hasilnya, seperti ini:

: `some_command`

Tentu saja Anda bisa melakukannya some_command > /dev/null, tetapi versi :-agak lebih pendek.

Yang sedang berkata saya tidak akan merekomendasikan benar-benar melakukan itu karena hanya akan membingungkan orang. Itu hanya terlintas dalam pikiran sebagai kemungkinan penggunaan.


25
Ini tidak aman jika perintah akan membuang beberapa megabyte output, karena shell buffer output dan kemudian meneruskannya sebagai argumen baris perintah (ruang stack) ke ':'.
Juliano

15

Ini juga berguna untuk program polyglot:

#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "$@"
~function(){ ... }

Ini sekarang kedua shell-script dieksekusi dan program JavaScript: berarti ./filename.js, sh filename.jsdan node filename.jssemua pekerjaan.

(Jelas sedikit penggunaan yang aneh, tetapi tetap efektif.)


Beberapa penjelasan, seperti yang diminta:

  • Skrip shell dievaluasi baris demi baris; dan execperintah, saat dijalankan, mengakhiri shell dan menggantikan prosesnya dengan perintah yang dihasilkan. Ini berarti bahwa untuk shell, programnya terlihat seperti ini:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Selama tidak ada perluasan parameter atau aliasing yang terjadi dalam kata, setiap kata dalam skrip shell dapat dibungkus dengan tanda kutip tanpa mengubah artinya; ini artinya ':'setara dengan :(kami hanya membungkusnya dengan tanda kutip di sini untuk mencapai semantik JavaScript yang dijelaskan di bawah)

  • ... dan seperti dijelaskan di atas, perintah pertama pada baris pertama adalah no-op (artinya diterjemahkan : //, atau jika Anda lebih suka mengutip kata-kata ':' '//',. Perhatikan bahwa //tidak ada arti khusus di sini, seperti halnya dalam JavaScript; itu hanya kata yang tidak berarti yang dibuang.)

  • Akhirnya, perintah kedua pada baris pertama (setelah titik koma), adalah daging asli dari program: itu adalah execpanggilan yang menggantikan skrip shell yang dipanggil , dengan proses Node.js dipanggil untuk mengevaluasi sisa skrip.

  • Sementara itu, baris pertama, dalam JavaScript, diurai sebagai string-literal ( ':'), dan kemudian komentar, yang dihapus; dengan demikian, untuk JavaScript, programnya terlihat seperti ini:

    ':'
    ~function(){ ... }

    Karena string-literal berada pada sebuah baris dengan sendirinya, itu adalah pernyataan no-op, dan dengan demikian dilucuti dari program; itu berarti bahwa seluruh baris dihapus, hanya menyisakan kode program Anda (dalam contoh ini, function(){ ... }tubuh.)


Halo, bisakah Anda menjelaskan apa : //;dan ~function(){}lakukan? Terima kasih:)
Stphane

1
@Stphane Menambahkan break-down! Adapun ~function(){}, itu sedikit lebih rumit. Ada sebuah beberapa jawaban lain di sini sentuhan pada itu, meskipun tidak satupun dari mereka benar-benar menjelaskannya untuk kepuasan saya ... jika tak satu pun dari pertanyaan-pertanyaan menjelaskan dengan baik-cukup untuk Anda, jangan ragu untuk posting sebagai pertanyaan di sini, saya akan senang menjawab secara mendalam tentang pertanyaan baru.
DAPAT DITERIMA

1
Saya tidak memperhatikan node. Jadi bagian fungsinya adalah tentang javascript! Saya baik-baik saja dengan operator unary di depan IIFE. Saya pikir ini terlalu bash di tempat pertama dan sebenarnya tidak benar-benar mendapatkan arti dari posting Anda. Saya baik-baik saja sekarang, terima kasih atas waktu yang Anda habiskan untuk menambahkan «kerusakan»!
Stphane

~{ No problem. (= }
DAPAT DITERIMA

12

Fungsi mendokumentasikan diri

Anda juga dapat menggunakan :untuk menanamkan dokumentasi dalam suatu fungsi.

Asumsikan Anda memiliki skrip perpustakaan mylib.sh, menyediakan berbagai fungsi. Anda bisa sumber perpustakaan ( . mylib.sh) dan memanggil fungsi langsung setelah itu ( lib_function1 arg1 arg2), atau menghindari mengacaukan namespace Anda dan memohon perpustakaan dengan argumen fungsi ( mylib.sh lib_function1 arg1 arg2).

Bukankah lebih baik jika Anda juga bisa mengetik mylib.sh --helpdan mendapatkan daftar fungsi yang tersedia dan penggunaannya, tanpa harus secara manual mempertahankan daftar fungsi dalam teks bantuan?

#! / bin / bash

# semua fungsi "publik" harus dimulai dengan awalan ini
LIB_PREFIX = 'lib_'

Fungsi perpustakaan "publik"
lib_function1 () {
    : Fungsi ini melakukan sesuatu yang rumit dengan dua argumen.
    :
    : Parameter:
    : 'arg1 - argumen pertama ($ 1)'
    : 'arg2 - argumen kedua'
    :
    : Hasil:
    : " ini rumit"

    # kode fungsi aktual dimulai di sini
}

lib_function2 () {
    : Dokumentasi fungsi

    # kode fungsi di sini
}

# fungsi bantuan
--Tolong() {
    gema MyLib v0.0.1
    gema
    echo Usage: mylib.sh [function_name [args]]
    gema
    echo Fungsi yang tersedia:
    mendeklarasikan -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"] \?; \? $ //; p}}'
}

# kode utama
if ["$ {BASH_SOURCE [0]}" = "$ {0}"]; kemudian
    # skrip dieksekusi alih-alih bersumber
    # aktifkan fungsi yang diminta atau tampilkan bantuan
    jika ["$ (ketik -t -" $ 1 "2> / dev / null)" = fungsi]; kemudian
        "$ @"
    lain
        --Tolong
    fi
fi

Beberapa komentar tentang kode:

  1. Semua fungsi "publik" memiliki awalan yang sama. Hanya ini yang dimaksudkan untuk dipanggil oleh pengguna, dan untuk dicantumkan dalam teks bantuan.
  2. Fitur mendokumentasikan diri bergantung pada titik sebelumnya, dan digunakan declare -funtuk menghitung semua fungsi yang tersedia, kemudian menyaringnya melalui sed untuk hanya menampilkan fungsi dengan awalan yang sesuai.
  3. Sebaiknya lampirkan dokumentasi dalam tanda kutip tunggal, untuk mencegah ekspansi yang tidak diinginkan dan penghapusan spasi putih. Anda juga harus berhati-hati saat menggunakan tanda kutip / kutipan dalam teks.
  4. Anda bisa menulis kode untuk menginternalisasi awalan pustaka, yaitu pengguna hanya perlu mengetik mylib.sh function1dan diterjemahkan secara internal lib_function1. Ini adalah latihan yang tersisa untuk pembaca.
  5. Fungsi bantuan bernama "--help". Ini adalah pendekatan yang mudah (yaitu malas) yang menggunakan mekanisme memanggil perpustakaan untuk menampilkan bantuan itu sendiri, tanpa harus kode pemeriksaan tambahan $1. Pada saat yang sama, itu akan mengacaukan namespace Anda jika Anda sumber perpustakaan. Jika Anda tidak suka itu, Anda dapat mengubah nama menjadi sesuatu seperti lib_helpatau benar-benar memeriksa args --helpdalam kode utama dan memanggil fungsi bantuan secara manual.

4

Saya melihat penggunaan ini dalam skrip dan berpikir itu adalah pengganti yang baik untuk memanggil nama dalam skrip.

oldIFS=$IFS  
IFS=/  
for basetool in $0 ; do : ; done  
IFS=$oldIFS  

... ini adalah pengganti kode: basetool=$(basename $0)


Saya lebih sukabasetool=${0##*/}
Amit Naidu
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.