$ # Có nghĩa là gì trong bash?


26

Tôi có một tập lệnh trong một tập tin có tên là:

echo "hello world"
echo ${1}

Và khi tôi chạy tập lệnh này bằng cách sử dụng:

./instance solfish

Tôi nhận được kết quả này:

hello world
solfish

Nhưng khi tôi chạy:

echo $# 

Nó nói "0". Tại sao? Tôi không hiểu $#nghĩa là gì .

Hãy giải thích nó.


10
Tại sao bạn chạy $#? Bạn muốn đạt được những gì? Nơi mà bạn đã nhận được lệnh này. Nó không liên quan gì cả.
Pilot6

7
@ Pilot6 trong trường hợp này anh ta hỏi ý nghĩa của một biến, đây không phải là trường hợp cụ thể vậy tại sao op cần cung cấp thông tin đó?
ADDB

21
@solfish Pilot đang cố gắng hiểu những gì bạn đang mong đợi. Bạn nói bạn đã chạy echo $#và nó trở lại 0là bình thường. Bạn đã ngạc nhiên về điều này, nhưng bạn không giải thích những gì bạn đang mong đợi hoặc tại sao bạn lại ngạc nhiên. Vì vậy, nó sẽ giúp chúng tôi cung cấp cho bạn một câu trả lời tốt hơn nếu bạn giải thích những gì bạn đang mong đợi. Những gì bạn nghĩ rằng echo $#sẽ làm. Nếu bạn chạy ./instance solfish./instancechứa echo $#, điều đó sẽ in 1và không 0.
terdon


1
Java cũng không sử dụng argc.
JakeRobb

Câu trả lời:


66

$#là một biến đặc biệt trong bash, mở rộng đến số lượng đối số (tham số vị trí) tức là $1, $2 ...được truyền cho tập lệnh đang đề cập hoặc shell trong trường hợp đối số được truyền trực tiếp vào shell, ví dụ như trong bash -c '...' .....

Điều này tương tự như argctrong C.


Có lẽ điều này sẽ làm cho nó rõ ràng:

$ bash -c 'echo $#'
0

$ bash -c 'echo $#' _ x
1

$ bash -c 'echo $#' _ x y
2

$ bash -c 'echo $#' _ x y z
3

Lưu ý rằng, bash -clấy đối số sau lệnh theo lệnh bắt đầu từ 0 ( $0; về mặt kỹ thuật, đó chỉ bashlà cách cho phép bạn đặt $0, không phải là đối số thực sự), vì vậy _được sử dụng ở đây chỉ như một trình giữ chỗ; đối số thực tế là x( $1), y( $2) và z( $3).


Tương tự, trong tập lệnh của bạn (giả sử script.sh) nếu bạn có:

#!/usr/bin/env bash
echo "$#"

Sau đó, khi bạn làm:

./script.sh foo bar

kịch bản sẽ xuất 2; tương tự như vậy,

./script.sh foo

sẽ xuất 1.


1
Tôi nghĩ rằng câu trả lời này là sai lệch. $ # là chỉ mục tối đa của mảng đối số. Nếu bạn đưa ra một đối số, giá trị đó sẽ có sẵn ở mức $ 0 và $ # sẽ có giá trị 0. Nếu bạn đưa ra hai đối số, chúng sẽ có giá trị $ 0 và $ 1 và $ # sẽ có giá trị 1.
JakeRobb

1
Hơn nữa, khi bạn sử dụng bash -c, hành vi sẽ khác với khi bạn chạy một tập lệnh shell thực thi, bởi vì trong trường hợp sau, đối số với chỉ số 0 là lệnh shell được sử dụng để gọi nó. Như vậy, tôi nghĩ cách khắc phục câu trả lời này là thay đổi nó để thực thi các tập lệnh dưới dạng tệp thay vì sử dụng bash -c, vì đó là cách người hỏi thực hiện nó.
JakeRobb

1
@JakeRobb Không. Đối với một tập lệnh, $0sẽ là chính tập lệnh; tranh luận sẽ được $1, $2, $3.... bash -chành vi là khác nhau vì nó có nghĩa là để sử dụng không tương tác và các đối số theo lệnh sẽ bắt đầu từ $0, tôi đã đề cập rõ ràng điều này tôi tin.
heemayl

5
@JakeRobb Bạn đã nghe nhầm tôi. Nếu bạn đưa ra một đối số, giá trị đó sẽ có sẵn ở mức $ 0 và $ # sẽ có giá trị 0 - điều này hoàn toàn sai.
heemayl

2
Nếu bạn đặt một chương trình hoàn chỉnh để xem nó lập luận bash -c, bạn nên chuyển bashmột số tên có ý nghĩa làm phụ cho đối số 0. bash -c 'echo "$@"' foo barchỉ in bar, bởi vì "$@"không bao gồm $0, giống như mọi khi. bash -ckhông "tạo $ 0 một trong các đối số", nó chỉ cho phép bạn đặt "$ 0" giống như cách bạn chạy chương trình với lệnh execvegọi hệ thống hoặc bất kỳ cách nào để ghi đè lên đối số 0. $0không bao giờ nên được coi là "một trong những lý lẽ".
Peter Cordes

17

echo $# xuất số lượng tham số vị trí của tập lệnh của bạn.

Bạn không có, vì vậy nó xuất ra 0.

echo $# là hữu ích bên trong tập lệnh, không phải là một lệnh riêng biệt.

Nếu bạn chạy một tập lệnh với một số tham số như

./instance par1 par2

việc echo $#đặt vào tập lệnh sẽ xuất ra 2.


6
Ví dụ về OP: Nếu bạn thêm dòng echo $#vào tập lệnh của mình và sau đó chạy lại, ./instance solfishbạn sẽ nhận được một dòng đầu ra bổ sung 1vì bạn đã cung cấp 1 tham số
derHugo

14

$#thường được sử dụng trong các tập lệnh bash để đảm bảo một tham số được thông qua. Nói chung, bạn kiểm tra một tham số trong phần đầu của tập lệnh.

Ví dụ: đây là một đoạn kịch bản tôi đang làm hôm nay:

if [[ $# -ne 1 ]]; then
    echo 'One argument required for file name, e.g. "Backup-2017-07-25"'
    echo '.tar will automatically be added as a file extension'
    exit 1
fi

Để tóm tắt $#báo cáo số lượng tham số được truyền cho một tập lệnh. Trong trường hợp của bạn, bạn đã vượt qua không có tham số và kết quả được báo cáo là 0.


Các #ứng dụng khác trong Bash

Việc #này thường được sử dụng trong bash để đếm số lần xuất hiện hoặc độ dài của một biến.

Để tìm độ dài của chuỗi:

myvar="some string"; echo ${#myvar}

trả về: 11

Để tìm số lượng phần tử mảng:

myArr=(A B C); echo ${#myArr[@]}

trả về: 3

Để tìm độ dài của phần tử mảng đầu tiên:

myArr=(A B C); echo ${#myArr[0]}

return: 1(Độ dài của A, 0 là phần tử đầu tiên vì các mảng sử dụng các chỉ số / chỉ số dựa trên zero).


$# -ne 1? Tại sao không $# -le 0hoặc tương đương?
Patrick Trentin

@PatrickTrentin Vì tôi không muốn họ truyền hai hoặc nhiều tham số. Như Thuyền trưởng đã nói trong "Tháng Mười Đỏ": "Chỉ một".
WinEunuuchs2Unix

Sau đó: "chính xác một đối số được yêu cầu ..." trong thông báo lỗi
Patrick Trentin

@PatrickTrentin Thông báo lỗi cho biết cách sử dụng tập lệnh với một ví dụ về cách sử dụng một đối số. Hơn nữa, tập lệnh sao lưu được gọi bởi cron.d Daily,
WinEunuuchs2Unix 26/07/17

Làm thế nào có liên quan tôi sẽ không biết. Dù sao, chúc mừng.
Patrick Trentin

10

$# là số lượng đối số, nhưng hãy nhớ nó sẽ khác nhau trong một hàm.

$#là số lượng tham số vị trí được truyền cho hàm script, shell hoặc shell . Điều này là do, trong khi một hàm shell đang chạy, các tham số vị trí tạm thời được thay thế bằng các đối số cho hàm . Điều này cho phép các hàm chấp nhận và sử dụng các tham số vị trí riêng của chúng.

Tập lệnh này luôn in 3, bất kể có bao nhiêu đối số được truyền cho chính tập lệnh, bởi vì "$#"trong hàm fmở rộng đến số lượng đối số được truyền cho hàm:

#!/bin/sh

f() {
    echo "$#"
}

f a b c

Điều này rất quan trọng vì nó có nghĩa là mã như thế này không hoạt động như bạn mong đợi, nếu bạn không quen với cách các tham số vị trí hoạt động trong các hàm shell:

#!/bin/sh

check_args() { # doesn't work!
    if [ "$#" -ne 2 ]; then
        printf '%s: error: need 2 arguments, got %d\n' "$0" "$#" >&2
        exit 1
    fi
}

# Maybe check some other things...
check_args
# Do other stuff...

Trong check_args, $#mở rộng đến số lượng đối số được truyền cho chính hàm, mà trong tập lệnh đó luôn là 0.

Nếu bạn muốn chức năng như vậy trong một hàm shell, thay vào đó bạn phải viết một cái gì đó như thế này:

#!/bin/sh

check_args() { # works -- the caller must pass the number of arguments received
    if [ "$1" -ne 2 ]; then
        printf '%s: error: need 2 arguments, got %d\n' "$0" "$1" >&2
        exit 1
    fi
}

# Maybe check some other things...
check_args "$#"

Điều này hoạt động vì $#được mở rộng bên ngoài hàm và được truyền cho hàm như một trong các tham số vị trí của nó . Bên trong hàm, $1mở rộng thành tham số vị trí đầu tiên được truyền cho hàm shell, thay vì cho tập lệnh mà nó là một phần của.

Như vậy, giống như $#, các thông số đặc biệt $1, $2vv, cũng như $@$*, cũng liên quan đến các đối số truyền cho một chức năng, khi họ được mở rộng trong hàm. Tuy nhiên, $0không không thay đổi tên của hàm, đó là lý do tôi vẫn còn có thể sử dụng nó để tạo ra một thông báo lỗi chất lượng.

$ ./check-args-demo a b c
./check-args-demo: error: need 2 arguments, got 3

Tương tự, nếu bạn xác định một hàm bên trong hàm khác, bạn đang làm việc với các tham số vị trí được truyền cho hàm trong cùng, trong đó việc mở rộng được thực hiện:

#!/bin/sh

outer() {
    inner() {
        printf 'inner() got %d arguments\n' "$#"
    }

    printf 'outer() got %d arguments\n' "$#"
    inner x y z
}

printf 'script got %d arguments\n' "$#"
outer p q

Tôi đã gọi kịch bản này nestedvà (sau khi chạy chmod +x nested) tôi đã chạy nó:

$ ./nested a
script got 1 arguments
outer() got 2 arguments
inner() got 3 arguments

Vâng tôi biết. "1 đối số" là một lỗi số nhiều.

Các tham số vị trí cũng có thể được thay đổi.

Nếu bạn đang viết một tập lệnh, các tham số vị trí bên ngoài hàm sẽ là các đối số dòng lệnh được truyền cho tập lệnh trừ khi bạn đã thay đổi chúng .

Một cách phổ biến để thay đổi chúng là với shiftnội trang, dịch chuyển từng tham số vị trí sang trái, bỏ cái đầu tiên và giảm $#1:

#!/bin/sh

while [ "$#"  -ne 0 ]; do
    printf '%d argument(s) remaining.\nGot "%s".\n\n' "$#" "$1"
    shift
done
$ ./do-shift foo bar baz      # I named the script do-shift.
3 argument(s) remaining.
Got "foo".

2 argument(s) remaining.
Got "bar".

1 argument(s) remaining.
Got "baz".

Chúng cũng có thể được thay đổi với setnội dung:

#!/bin/sh

printf '%d args: %s\n' "$#" "$*"
set foo bar baz
printf '%d args: %s\n' "$#" "$*"
$ ./set-args a b c d e      # I named the script set-args.
5 args: a b c d e
3 args: foo bar baz

1
Kudos cho câu trả lời giáo dục vượt xa những gì được hỏi nhưng đáp ứng ý định học tập của người hỏi ban đầu và những người khác như tôi!
Ricardo
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.