Co oznacza $ # w bash?

26

Mam skrypt w pliku o nazwie wystąpienie:

echo "hello world"
echo ${1}

A kiedy uruchamiam ten skrypt za pomocą:

./instance solfish

Otrzymuję ten wynik:

hello world
solfish

Ale kiedy biegnę:

echo $# 

Mówi „0”. Czemu? Nie rozumiem, co $#to znaczy.

Proszę wyjaśnij to.

solfish
źródło
10
Dlaczego uruchomić $#? Co chcesz osiągnąć Skąd masz to polecenie? To w ogóle nie ma znaczenia.
Pilot6
7
@ Pilot6 w tym przypadku pyta o znaczenie zmiennej, nie jest to konkretny przypadek, więc dlaczego operacja miałaby podawać tę informację?
ADDB,
21
@solfish Pilot próbuje zrozumieć, czego się spodziewałeś. Powiedziałeś, że uciekłeś echo $#i wrócił, 0co jest normalne. Zaskoczyło Cię to, ale nie wyjaśniłeś, czego się spodziewałeś ani dlaczego. Pomoże nam to udzielić lepszej odpowiedzi, jeśli wyjaśnisz, czego się spodziewałeś. Co myślałeś, że to echo $#by zrobiło. Gdybyś uciekał ./instance solfishi ./instancezawierał echo $#, to by to wydrukowało, 1a nie 0.
terdon
1
Java również nie używa argc.
JakeRobb

Odpowiedzi:

66

$#jest specjalną zmienną w bash, która rozwija się do liczby argumentów (parametrów pozycyjnych) tj. $1, $2 ...przekazywanych do danego skryptu lub powłoki w przypadku argumentów przekazywanych bezpośrednio do powłoki np bash -c '...' ..... w .

Jest to podobne do argcw C.


Być może to wyjaśni:

$ bash -c 'echo $#'
0

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

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

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

Zauważ, że bash -cpobiera argument po następującej po nim komendzie, zaczynając od 0 ( $0; technicznie jest to po prostu bashsposób na ustawienie $0, a nie argument tak naprawdę), więc _jest tutaj używany jako symbol zastępczy; rzeczywistymi argumentami są x( $1), y( $2) i z( $3).


Podobnie w twoim skrypcie (zakładając script.sh), jeśli masz:

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

Kiedy to zrobisz:

./script.sh foo bar

skrypt wyświetli 2; również,

./script.sh foo

wyświetli 1.

heemayl
źródło
1
Myślę, że ta odpowiedź jest myląca. $ # to maksymalny indeks tablicy argumentów. Jeśli podasz jeden argument, będzie on dostępny za 0 USD, a $ # będzie miał wartość 0. Jeśli podasz dwa argumenty, będą to 0 i 1 USD, a $ # będzie miał wartość 1.
JakeRobb
1
Ponadto podczas używania bash -czachowanie jest inne niż w przypadku uruchamiania wykonywalnego skryptu powłoki, ponieważ w tym drugim przypadku argument o indeksie 0 jest poleceniem powłoki używanym do jego wywołania. Jako taki, myślę, że sposobem na poprawienie tej odpowiedzi jest zmiana jej wykonywania skryptów jako plików zamiast używania bash -c, ponieważ w ten sposób pytający to robił.
JakeRobb
1
@JakeRobb Nie. Skryptem $0byłby sam skrypt; byłyby argumenty $1, $2, $3.... bash -czachowanie jest inne, ponieważ jest przeznaczone do użytku nieinteraktywnego, a argumenty następujące po poleceniu zaczną się $0, o czym wyraźnie wspomniałem.
heemayl
5
@JakeRobb Nie zrozumiałeś mnie. Jeśli podasz jeden argument, będzie on dostępny za 0 USD, a $ # będzie miał wartość 0 - to po prostu źle.
heemayl
2
Jeśli umieścisz pełny program, który patrzy na to, argumenty bash -c, powinieneś bashpodać jakąś znaczącą nazwę jako wypełniacz dla 0. arg. bash -c 'echo "$@"' foo bardrukuje tylko bar, ponieważ "$@"nie obejmuje $0, tak jak zawsze. bash -cnie „make $ 0 jeden z argumentami”, a jedynie pozwala na ustawienie „$ 0” w taki sam sposób, gdy uruchomiony program z tej execvefunkcji systemowej lub jakikolwiek sposób płaszcza przesłanianie 0TH arg. $0nigdy nie powinien być uważany za „jeden z argumentów”.
Peter Cordes
17

echo $# wyświetla liczbę parametrów pozycyjnych skryptu.

Nie masz, więc wyprowadza 0.

echo $# jest przydatny w skrypcie, a nie jako osobne polecenie.

Jeśli uruchomisz skrypt z niektórymi parametrami, takimi jak

./instance par1 par2

echo $#umieszczane w skryptu pokaże na wyjściu 2.

Pilot 6
źródło
6
Na przykład OP: Jeśli dodasz wiersz echo $#do skryptu i uruchomisz ponownie ./instance solfish, powinieneś otrzymać dodatkową linię wyjściową, 1ponieważ podałeś 1 parametr
derHugo
14

$#jest zwykle używany w skryptach bash, aby zapewnić przekazanie parametru. Ogólnie rzecz biorąc, sprawdzasz parametr na początku skryptu.

Oto na przykład fragment skryptu, nad którym dzisiaj pracowałem:

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

Podsumowując $#raporty, liczba parametrów przekazanych do skryptu. W twoim przypadku nie przekazałeś żadnych parametrów, a raportowany wynik to 0.


Inne #zastosowania w Bash

#Jest często używany w bash policzyć liczbę wystąpień lub długość zmiennej.

Aby znaleźć długość łańcucha:

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

zwraca: 11

Aby znaleźć liczbę elementów tablicy:

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

zwraca: 3

Aby znaleźć długość pierwszego elementu tablicy:

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

zwraca: 1(Długość A0 jest pierwszym elementem, ponieważ tablice używają zerowych indeksów / indeksów).

WinEunuuchs2Unix
źródło
$# -ne 1? Dlaczego nie $# -le 0lub równoważny?
Patrick Trentin
@PatrickTrentin Ponieważ nie chcę, aby przekazywały dwa lub więcej parametrów. Jak powiedział Kapitan w „Czerwonym październiku”: „Tylko jeden”.
WinEunuuchs2Unix
Następnie: „wymagany dokładnie jeden argument ...” w komunikacie o błędzie
Patrick Trentin
@PatrickTrentin Komunikat o błędzie określa sposób użycia skryptu na przykładzie użycia jednego argumentu. Ponadto skrypt kopii zapasowej jest wywoływany przez cron.daily, który jest konfigurowany tylko raz przez kogoś znającego się na
WinEunuuchs2Unix
Jak to jest istotne, nie wiedziałbym. W każdym razie, na zdrowie.
Patrick Trentin
10

$# to liczba argumentów, ale pamiętaj, że będzie różna w funkcji.

$#to liczba parametrów pozycyjnych przekazanych do skryptu, powłoki lub funkcji powłoki . Jest tak, ponieważ podczas działania funkcji powłoki parametry pozycyjne są tymczasowo zastępowane argumentami funkcji . Pozwala to funkcjom akceptować i wykorzystywać własne parametry pozycyjne.

Ten skrypt zawsze drukuje 3, niezależnie od liczby argumentów przekazanych do samego skryptu, ponieważ "$#"w funkcji frozwija się do liczby argumentów przekazanych do funkcji:

#!/bin/sh

f() {
    echo "$#"
}

f a b c

Jest to ważne, ponieważ oznacza to, że taki kod nie działa zgodnie z oczekiwaniami, jeśli nie znasz sposobu działania parametrów pozycyjnych w funkcjach powłoki:

#!/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...

In check_args, $#rozwija się do liczby argumentów przekazanych samej funkcji, która w tym skrypcie ma zawsze wartość 0.

Jeśli chcesz taką funkcjonalność w funkcji powłoki, musisz zamiast tego napisać coś takiego:

#!/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 "$#"

Działa $#to, ponieważ jest rozszerzone poza funkcję i przekazane do funkcji jako jeden z jej parametrów pozycyjnych. Wewnątrz funkcji $1rozwija się do pierwszego parametru pozycyjnego, który został przekazany do funkcji powłoki, a nie do skryptu, którego jest częścią.

Tak więc, jak $#, specjalne parametry $1, $2itp, jak $@i $*również odnosić się do argumentów przekazywanych do funkcji, gdy są one w rozbudowanych funkcji. Jednakże, $0nie nie zmieni się nazwa funkcji, dlatego byłem jeszcze w stanie wykorzystać go do produkcji komunikat o błędzie jakości.

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

Podobnie, jeśli zdefiniujesz jedną funkcję w drugiej, pracujesz z parametrami pozycyjnymi przekazanymi do najbardziej wewnętrznej funkcji, w której przeprowadzane jest rozwinięcie:

#!/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

Wywołałem ten skrypt nestedi (po uruchomieniu chmod +x nested) uruchomiłem go:

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

Tak, wiem. „1 argumenty” to błąd pluralizacji.

Parametry pozycyjne można również zmienić.

Jeśli piszesz skrypt, parametrami pozycyjnymi poza funkcją będą argumenty wiersza poleceń przekazywane do skryptu, chyba że je zmieniłeś .

Jednym z powszechnych sposobów ich zmiany jest shiftwbudowane, które przesuwa każdy parametr pozycyjny w lewo o jeden, upuszczając pierwszy i zmniejszając $#o 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".

Można je również zmienić za pomocą setwbudowanego:

#!/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
Eliah Kagan
źródło
1
Wyrazy uznania dla odpowiedzi edukacyjnej, która wykraczała poza to, o co pytano, ale odpowiadała na intencje uczenia się pierwotnego pytającego i innych takich jak ja!
Ricardo