Mục đích của GNU Bash (dấu hai chấm) là gì?


335

Mục đích của một lệnh không làm gì, ít hơn một người lãnh đạo bình luận, nhưng thực sự là một vỏ được xây dựng trong chính nó?

Nó chậm hơn việc chèn một bình luận vào tập lệnh của bạn khoảng 40% mỗi cuộc gọi, có thể thay đổi rất nhiều tùy thuộc vào kích thước của bình luận. Những lý do duy nhất có thể tôi có thể thấy cho nó là:

# 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

Tôi đoán những gì tôi thực sự tìm kiếm là ứng dụng lịch sử mà nó có thể có.



68
@Caleb - Tôi đã hỏi cái này hai năm trước cái kia.
amphetamachine

Tôi sẽ không nói một lệnh trả về một giá trị cụ thể "không làm gì cả." Trừ khi lập trình chức năng bao gồm "không làm gì cả." :-)
LarsH

Câu trả lời:


415

Trong lịch sử , đạn Bourne không có truefalsenhư là các lệnh tích hợp. truethay vào đó chỉ đơn giản là bí danh :falsevới một cái gì đó như let 0.

:tốt hơn một chút so truevới tính di động đối với đạn pháo có nguồn gốc từ Bourne cổ đại. Một ví dụ đơn giản, xem xét việc không có !toán tử đường ống cũng như ||toán tử danh sách (như trường hợp của một số shell Bourne cổ đại). Điều này để lại elsemệnh đề của ifcâu lệnh là phương tiện duy nhất để phân nhánh dựa trên trạng thái thoát:

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

ifyêu cầu một thenmệnh đề không trống và các bình luận không được tính là không trống, :đóng vai trò là không có.

Ngày nay (nghĩa là: trong một bối cảnh hiện đại) bạn thường có thể sử dụng :hoặc true. Cả hai đều được chỉ định bởi POSIX và một số tìm thấy truedễ đọc hơn. Tuy nhiên, có một điểm khác biệt thú vị: :được gọi là POSIX đặc biệt tích hợp , trong khi đó truelà tích hợp thông thường .

  • Xây dựng đặc biệt được yêu cầu phải được xây dựng vào vỏ; Các phần dựng sẵn thông thường chỉ được tích hợp "thông thường", nhưng nó không được bảo đảm nghiêm ngặt. Thường không nên có một chương trình thông thường được đặt tên :với chức năng truetrong PATH của hầu hết các hệ thống.

  • Có lẽ sự khác biệt quan trọng nhất là với các tích hợp đặc biệt, bất kỳ biến nào được tích hợp sẵn - ngay cả trong môi trường khi đánh giá lệnh đơn giản - vẫn tồn tại sau khi lệnh hoàn thành, như được trình bày ở đây bằng cách sử dụng ksh93:

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

    Lưu ý rằng Zsh bỏ qua yêu cầu này, cũng như GNU Bash ngoại trừ khi hoạt động ở chế độ tương thích POSIX, nhưng tất cả các trình bao "POSIX sh có nguồn gốc" khác đều quan sát điều này bao gồm dash, ksh93 và mksh.

  • Một điểm khác biệt nữa là các phần dựng sẵn thông thường phải tương thích với exec- được trình bày ở đây bằng Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX cũng lưu ý rõ ràng rằng :có thể nhanh hơn true, mặc dù đây tất nhiên là một chi tiết cụ thể thực hiện.


Ý bạn là các phần dựng sẵn thông thường phải không tương thích với exec?
Old Pro

1
@OldPro: Không, anh ấy đúng trong đó truelà một nội dung thông thường, nhưng anh ấy không chính xác trong đó execlà sử dụng /bin/truethay vì nội dung.
Tạm dừng cho đến khi có thông báo mới.

1
@DennisWilliamson Tôi chỉ đi bằng cách thông số kỹ thuật được diễn đạt. Tất nhiên hàm ý là các nội trang thông thường cũng nên có phiên bản độc lập.
ormaaj

17
+1 Câu trả lời xuất sắc. Tôi vẫn muốn lưu ý việc sử dụng để khởi tạo các biến, như : ${var?not initialized}et al.
tripleee

Một theo dõi ít ​​nhiều không liên quan. Bạn nói :là một đặc biệt tích hợp và không nên có một chức năng được đặt tên bởi nó. Nhưng không phải là ví dụ thường thấy nhất về bom ngã ba :(){ :|: & };:đặt tên một chức năng với tên :?
Chong

63

Tôi sử dụng nó để dễ dàng kích hoạt / vô hiệu hóa các lệnh biến:

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

$vecho "Verbose echo is ON"

Như vậy

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

Điều này làm cho một kịch bản sạch. Điều này không thể được thực hiện với '#'.

Cũng thế,

: >afile

là một trong những cách đơn giản nhất để đảm bảo rằng 'afile' tồn tại nhưng có độ dài bằng 0.


20
>afilethậm chí còn đơn giản hơn và đạt được hiệu quả tương tự.
bá tước

2
Thật tuyệt, tôi sẽ sử dụng thủ thuật $ vecho đó để đơn giản hóa các tập lệnh mà tôi đang duy trì.
BarneySchmale

5
Lợi ích để trích dẫn đại tràng trong là vecho=":"gì? Chỉ để dễ đọc?
leoj

56

Một ứng dụng hữu ích cho: là nếu bạn chỉ quan tâm đến việc sử dụng các mở rộng tham số cho các hiệu ứng phụ của chúng chứ không thực sự chuyển kết quả của chúng cho một lệnh. Trong trường hợp đó, bạn sử dụng PE làm đối số cho một trong hai: hoặc sai tùy thuộc vào việc bạn muốn trạng thái thoát là 0 hay 1. Một ví dụ có thể là : "${var:=$1}". Vì :là một nội dung nên nó sẽ khá nhanh.


2
Bạn cũng có thể sử dụng nó cho các tác dụng phụ của việc mở rộng số học: : $((a += 1))( ++và các --toán tử không cần phải được thực hiện theo POSIX.). Trong bash, ksh và các shell khác có thể bạn cũng có thể làm: ((a += 1))hoặc ((a++))nhưng nó không được chỉ định bởi POSIX.
pabouk

@pabouk Yep tất cả đều đúng, mặc dù (())được chỉ định là một tính năng tùy chọn. "Nếu một chuỗi ký tự bắt đầu bằng" (("sẽ được phân tách bởi shell như là một khai triển số học nếu trước '$', các shell thực hiện một phần mở rộng theo đó" (biểu thức)) "được đánh giá là biểu thức số học có thể coi là biểu thức số học có thể coi "((" như giới thiệu như một đánh giá số học thay vì lệnh nhóm. "
ormaaj

50

:cũng có thể dành cho nhận xét khối (tương tự / * * / trong ngôn ngữ C). Ví dụ: nếu bạn muốn bỏ qua một khối mã trong tập lệnh của mình, bạn có thể làm điều này:

: << 'SKIP'

your code block here

SKIP

3
Ý kiến ​​tồi. Bất kỳ thay thế lệnh nào trong tài liệu ở đây vẫn được xử lý.
chepner

33
Không phải là một ý tưởng tồi. Bạn có thể tránh độ phân giải / thay thế trong tài liệu ở đây bằng cách trích dẫn một dấu phân cách :: << 'SKIP'
Rondo

1
IIRC bạn cũng có thể \ thoát bất kỳ ký tự phân cách nào cho cùng một hiệu ứng : : <<\SKIP.
yyny

31

Nếu bạn muốn cắt một tệp thành 0 byte, hữu ích để xóa nhật ký, hãy thử điều này:

:> file.log

16
> file.loglà đơn giản hơn và đạt được hiệu quả tương tự.
amphetamachine

55
Yah, nhưng khuôn mặt hạnh phúc là những gì nó làm cho tôi:>
Ahi Tuna

23
@amphetamachine: :>dễ mang theo hơn. Một số shell (chẳng hạn như của tôi zsh) tự động khởi tạo một con mèo trong shell hiện tại và lắng nghe stdin khi được chuyển hướng không có lệnh. Thay vì cat /dev/null, :đơn giản hơn nhiều. Thông thường hành vi này là khác nhau trong các vỏ tương tác hơn là các tập lệnh, nhưng nếu bạn viết tập lệnh theo cách cũng hoạt động tương tác, việc gỡ lỗi bằng cách sao chép-dán sẽ dễ dàng hơn nhiều.
Caleb

2
Làm thế nào : > filekhác với true > file(ngoài số lượng nhân vật và khuôn mặt hạnh phúc) trong một lớp vỏ hiện đại (giả sử :truenhanh như nhau)?
Adam Katz


29

Hai cách sử dụng khác không được đề cập trong các câu trả lời khác:

Ghi nhật ký

Lấy kịch bản ví dụ này:

set -x
: Logging message here
example_command

Dòng đầu tiên set -x, làm cho shell in ra lệnh trước khi chạy nó. Đây là một công trình khá hữu ích. Nhược điểm là echo Log messageloại câu lệnh thông thường hiện in thông điệp hai lần. Phương pháp đại tràng được làm tròn đó. Lưu ý rằng bạn vẫn sẽ phải thoát các ký tự đặc biệt giống như bạn làmecho .

Chức danh công việc

Tôi đã thấy nó được sử dụng trong các công việc định kỳ, như thế này:

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

Đây là một công việc định kỳ chạy kịch bản /opt/backup.shmỗi ngày lúc 10:45. Ưu điểm của kỹ thuật này là nó làm cho các đối tượng email tìm kiếm tốt hơn khi /opt/backup.shin một số đầu ra.


Vị trí nhật ký mặc định ở đâu? Tôi có thể đặt vị trí nhật ký không? Là mục đích nhiều hơn để tạo đầu ra trong thiết bị xuất chuẩn trong quá trình script / nền?
domdambrogia

1
@domdambrogia Khi sử dụng set -x, các lệnh được in ra (bao gồm cả những thứ như : foobar) đi đến stderr.
Flimm

22

Bạn có thể sử dụng nó kết hợp với backticks ( ``) để thực thi lệnh mà không hiển thị đầu ra của nó, như thế này:

: `some_command`

Tất nhiên bạn chỉ có thể làm some_command > /dev/null, nhưng sự đảo ngược :có phần ngắn hơn.

Điều đó được nói rằng tôi sẽ không thực sự làm điều đó vì nó sẽ chỉ khiến mọi người nhầm lẫn. Nó chỉ đến với tâm trí như một trường hợp sử dụng có thể.


25
Điều này không an toàn nếu lệnh sẽ chuyển một vài megabyte đầu ra, vì shell đệm đầu ra và sau đó chuyển nó dưới dạng đối số dòng lệnh (không gian ngăn xếp) thành ':'.
Juliano

15

Nó cũng hữu ích cho các chương trình polyglot:

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

Điều này bây giờ là cả một vỏ kịch bản thực thi một chương trình JavaScript: nghĩa ./filename.js, sh filename.jsnode filename.js tất cả công việc.

(Chắc chắn là một chút sử dụng lạ, nhưng dù sao cũng hiệu quả.)


Một số giải thích, theo yêu cầu:

  • Shell-script được đánh giá từng dòng một; và execlệnh, khi chạy, kết thúc shell và thay thế tiến trình của nó bằng lệnh kết quả. Điều này có nghĩa là với shell, chương trình trông như thế này:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Miễn là không có sự mở rộng tham số hoặc bí danh nào xảy ra trong từ, bất kỳ từ nào trong shell-script đều có thể được gói trong dấu ngoặc kép mà không thay đổi nghĩa của nó; điều này có nghĩa ':'là tương đương với :(chúng tôi chỉ gói nó trong dấu ngoặc kép ở đây để đạt được ngữ nghĩa JavaScript được mô tả bên dưới)

  • ... và như được mô tả ở trên, lệnh đầu tiên trên dòng đầu tiên là no-op (nó dịch sang : //hoặc nếu bạn muốn trích dẫn các từ , ':' '//'. Lưu ý rằng //mang không có ý nghĩa đặc biệt nào ở đây, như trong JavaScript; nó chỉ là một từ vô nghĩa bị vứt bỏ.)

  • Cuối cùng, lệnh thứ hai trên dòng đầu tiên (sau dấu chấm phẩy), là phần thực sự của chương trình: đó là lệnh execgọi thay thế tập lệnh shell được gọi , với quy trình Node.js được gọi để đánh giá phần còn lại của tập lệnh.

  • Trong khi đó, dòng đầu tiên, trong JavaScript, phân tích cú pháp dưới dạng chuỗi ký tự ( ':') và sau đó là một nhận xét, bị xóa; do đó, với JavaScript, chương trình trông như thế này:

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

    Vì chính chuỗi ký tự nằm trên một dòng, nó là một câu lệnh không op, và do đó bị tước khỏi chương trình; điều đó có nghĩa là toàn bộ dòng bị xóa, chỉ để lại mã chương trình của bạn (trong ví dụ này là phần function(){ ... }thân.)


Xin chào, bạn có thể giải thích những gì : //;~function(){}làm gì? Cảm ơn bạn:)
Stphane

1
@Stphane Đã thêm một sự cố! Đối với ~function(){}, đó là một chút phức tạp. Có một vài câu trả lời khác ở đây chạm vào nó, mặc dù không ai trong số họ thực sự giải thích điều đó với sự hài lòng của tôi. vui vẻ trả lời sâu về một câu hỏi mới.
ELLIOTTCABLE

1
Tôi đã không chú ý đến node. Vì vậy, phần chức năng là tất cả về javascript! Tôi là okey với nhà điều hành unary trước IIFE. Tôi nghĩ rằng đây là bash quá ngay từ đầu và thực sự không thực sự hiểu ý nghĩa của bài viết của bạn. Bây giờ tôi là okey, cảm ơn bạn đã dành thời gian thêm «chia nhỏ»!
Stphane

~{ No problem. (= }
ELLIOTTCABLE

12

Chức năng tự viết tài liệu

Bạn cũng có thể sử dụng :để nhúng tài liệu trong một chức năng.

Giả sử bạn có một tập lệnh thư viện mylib.sh, cung cấp nhiều chức năng. Bạn có thể nguồn thư viện ( . mylib.sh) và gọi các hàm trực tiếp sau đó ( lib_function1 arg1 arg2) hoặc tránh làm lộn xộn không gian tên của bạn và gọi thư viện bằng một đối số hàm (mylib.sh lib_function1 arg1 arg2 ).

Sẽ không hay nếu bạn cũng có thể nhập mylib.sh --helpvà nhận danh sách các chức năng có sẵn và cách sử dụng chúng, mà không phải duy trì thủ công danh sách chức năng trong văn bản trợ giúp?

#! / bin / bash

# tất cả các chức năng "công khai" phải bắt đầu bằng tiền tố này
LIB_PREFIX = 'lib_'

# chức năng thư viện "công cộng"
lib_feft1 () {
    : Hàm này thực hiện một số thứ phức tạp với hai đối số.
    :
    : Thông số:
    : 'arg1 - đối số đầu tiên ($ 1)'
    : 'arg2 - đối số thứ hai'
    :
    : Kết quả:
    : " Nó phức tạp lắm"

    # mã chức năng thực tế bắt đầu từ đây
}

lib_feft2 () {
    : Tài liệu chức năng

    # mã chức năng ở đây
}

# chức năng trợ giúp
--Cứu giúp() {
    tiếng vang MyLib v0.0.1
    tiếng vang
    Cách sử dụng tiếng vang: mylib.sh [function_name [args]]
    tiếng vang
    echo Các chức năng có sẵn:
    khai báo -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"] \ ?; \? $ //; p}}'
}

# Mã chính
if ["$ {BASH_SOURCE [0]}" = "$ {0}"]; sau đó
    # tập lệnh đã được thực thi thay vì nguồn gốc
    # gọi chức năng được yêu cầu hoặc hiển thị trợ giúp
    if ["$ (loại -t -" $ 1 "2> / dev / null)" = function]; sau đó
        "$ @"
    khác
        --Cứu giúp
    fi
fi

Một vài bình luận về mã:

  1. Tất cả các chức năng "công khai" có cùng một tiền tố. Chỉ những thứ này có nghĩa là được người dùng gọi ra và được liệt kê trong văn bản trợ giúp.
  2. Tính năng tự ghi lại dựa trên điểm trước đó và sử dụng declare -fđể liệt kê tất cả các chức năng có sẵn, sau đó lọc chúng thông qua sed để chỉ hiển thị các chức năng với tiền tố thích hợp.
  3. Đó là một ý tưởng tốt để gửi tài liệu trong dấu ngoặc đơn, để ngăn chặn mở rộng không mong muốn và loại bỏ khoảng trắng. Bạn cũng cần cẩn thận khi sử dụng dấu nháy đơn / dấu ngoặc kép trong văn bản.
  4. Bạn có thể viết mã để nội hóa tiền tố thư viện, tức là người dùng chỉ phải gõ mylib.sh function1và nó được dịch nội bộ sang lib_function1. Đây là một bài tập để lại cho người đọc.
  5. Chức năng trợ giúp được đặt tên là "- trợ giúp". Đây là một cách tiếp cận thuận tiện (tức là lười biếng) sử dụng cơ chế gọi thư viện để hiển thị chính trợ giúp mà không cần phải kiểm tra thêm mã $1. Đồng thời, nó sẽ làm lộn xộn không gian tên của bạn nếu bạn lấy thư viện. Nếu bạn không thích điều đó, bạn có thể thay đổi tên thành một cái gì đó giống như lib_helphoặc thực sự kiểm tra các đối số --helptrong mã chính và gọi hàm trợ giúp theo cách thủ công.

4

Tôi đã thấy cách sử dụng này trong một tập lệnh và nghĩ rằng nó là một sự thay thế tốt cho việc gọi tên cơ sở trong một tập lệnh.

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

... đây là sự thay thế cho mã: basetool=$(basename $0)


Tôi thíchbasetool=${0##*/}
Amit N.9
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.