Was bedeutet $ # in Bash?

26

Ich habe ein Skript in einer Datei namens instance:

echo "hello world"
echo ${1}

Und wenn ich dieses Skript mit:

./instance solfish

Ich bekomme diese Ausgabe:

hello world
solfish

Aber wenn ich renne:

echo $# 

Es sagt "0". Warum? Ich verstehe nicht was das $#bedeutet.

Bitte erkläre es.

Solfish
quelle
10
Warum rennst du $#? Was willst du erreichen Woher hast du diesen Befehl? Es ist überhaupt nicht relevant.
Pilot6
7
@Pilot6 in diesem Fall fragt er nach der Bedeutung einer Variablen. Dies ist nicht fallspezifisch. Warum sollte der OP diese Informationen geben müssen?
ADDB
21
@solfish Pilot versucht zu verstehen, was Sie erwartet hatten. Du hast gesagt, du bist gelaufen echo $#und es ist wieder da, 0was normal ist. Das hat Sie überrascht, aber Sie erklären nicht, was Sie erwartet haben oder warum Sie überrascht waren. Es würde uns also helfen, Ihnen eine bessere Antwort zu geben, wenn Sie erklären, was Sie erwartet haben. Was Sie dachten, echo $#würde das tun. Wenn Sie lief ./instance solfishund ./instanceenthielt echo $#, würde das drucken 1und nicht 0.
Terdon
1
Java verwendet auch kein argc.
JakeRobb

Antworten:

66

$#ist eine spezielle Variable in bash, die um die Anzahl der Argumente (Positionsparameter) erweitert wird, dh $1, $2 ...an das betreffende Skript oder die Shell übergeben wird, wenn Argumente direkt an die Shell übergeben werden, z bash -c '...' ..... B. in .

Dies ist ähnlich wie argcin C.


Vielleicht wird dadurch klar:

$ bash -c 'echo $#'
0

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

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

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

Beachten Sie, dass bash -cArgumente nach dem darauf folgenden Befehl ab 0 verwendet werden ( $0technisch gesehen ist dies nur bashdie Art und Weise, Sie festlegen zu lassen $0, kein wirkliches Argument). Sie _werden hier also nur als Platzhalter verwendet. Die tatsächlichen Argumente sind x( $1), y( $2) und z( $3).


Ebenso in Ihrem Skript (vorausgesetzt script.sh), wenn Sie haben:

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

Wenn Sie dann tun:

./script.sh foo bar

das Skript gibt 2 aus; Gleichfalls,

./script.sh foo

gibt 1 aus.

heemayl
quelle
1
Ich denke, diese Antwort ist irreführend. $ # ist der maximale Index des Array von Argumenten. Wenn Sie ein Argument angeben, wird dieses bei $ 0 verfügbar sein und $ # wird den Wert 0 haben. Wenn Sie zwei Argumente
angeben
1
Außerdem bash -cunterscheidet sich das Verhalten bei der Verwendung von Shell-Skripten von dem bei der Ausführung eines ausführbaren Shell-Skripts, da im letzteren Fall das Argument mit dem Index 0 der Shell-Befehl ist, mit dem es aufgerufen wird. Aus diesem Grund besteht die Möglichkeit, diese Antwort zu bash -ckorrigieren, darin, sie so zu ändern, dass sie Skripte als Dateien ausführt, anstatt sie zu verwenden , da der Fragesteller dies getan hat.
JakeRobb
1
@ JakeRobb Nein. Für ein Skript $0wäre das Skript selbst; Argumente wären $1, $2, $3.... bash -cVerhalten ist anders, da es für die nicht interaktive Verwendung gedacht ist und die Argumente, die dem Befehl folgen, beginnen würden. $0Ich glaube, ich habe dies klar erwähnt.
Heemayl
5
@JakeRobb Du hast mich verhört. Wenn Sie ein Argument angeben, ist dieses bei $ 0 verfügbar, und $ # hat den Wert 0 - dies ist eindeutig falsch.
Heemayl
2
Wenn Sie ein vollständiges Programm einfügen, in das es eingebettet ist bash -c, sollten Sie basheinen aussagekräftigen Namen als Füllzeichen für das 0. Argument übergeben. bash -c 'echo "$@"' foo bardruckt nur bar, weil "$@"nicht enthalten $0, wie immer. bash -cmacht "$ 0 nicht zu einem der Argumente", sondern ermöglicht es Ihnen lediglich, "$ 0" auf dieselbe Weise wie beim Ausführen eines Programms mit dem execveSystemaufruf oder einer Shell-Methode zum Überschreiben des 0. Arguments festzulegen. $0sollte niemals als "einer der Gründe" angesehen werden.
Peter Cordes
17

echo $# gibt die Anzahl der Positionsparameter Ihres Skripts aus.

Sie haben keine und geben 0 aus.

echo $# ist nützlich innerhalb des Skripts, nicht als separater Befehl.

Wenn Sie ein Skript mit einigen Parametern wie ausführen

./instance par1 par2

Das echo $#im Skript platzierte wird 2 ausgeben.

Pilot6
quelle
6
Zum Beispiel für das OP: Wenn Sie die Zeile echo $#zu Ihrem Skript hinzufügen und dann erneut ausführen ./instance solfish, sollten Sie eine zusätzliche Ausgabezeile mit erhalten, 1da Sie 1 Parameter angegeben haben
derHugo
14

$#wird normalerweise in Bash-Skripten verwendet, um sicherzustellen, dass ein Parameter übergeben wird. Im Allgemeinen suchen Sie am Anfang Ihres Skripts nach einem Parameter.

Zum Beispiel ist hier ein Ausschnitt eines Skripts, an dem ich heute gearbeitet habe:

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

Um $#Berichte zusammenzufassen , geben Sie die Anzahl der an ein Skript übergebenen Parameter an. In Ihrem Fall haben Sie keine Parameter übergeben und das gemeldete Ergebnis ist 0.


Andere #Verwendungen in Bash

Das #wird häufig in Bash verwendet, um die Anzahl der Vorkommen oder die Länge einer Variablen zu zählen.

So ermitteln Sie die Länge einer Zeichenfolge:

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

kehrt zurück: 11

So ermitteln Sie die Anzahl der Array-Elemente:

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

kehrt zurück: 3

So ermitteln Sie die Länge des ersten Array-Elements:

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

Gibt Folgendes zurück: 1(Die Länge von A, 0 ist das erste Element, da Arrays auf Null basierende Indizes / Indizes verwenden.)

WinEunuuchs2Unix
quelle
$# -ne 1? Warum nicht $# -le 0oder gleichwertig?
Patrick Trentin
@PatrickTrentin Weil ich nicht möchte, dass sie zwei oder mehr Parameter übergeben. Wie der Kapitän im "Roten Oktober" sagte: "Nur einer".
WinEunuuchs2Unix
Dann: "genau ein Argument erforderlich ..." in der Fehlermeldung
Patrick Trentin
@PatrickTrentin Die Fehlermeldung beschreibt die Verwendung des Skripts anhand eines Beispiels für die Verwendung eines Arguments. Außerdem wird das Backup-Skript von cron.daily aufgerufen, das nur einmal von einem technisch versierten Benutzer
WinEunuuchs2Unix
Wie das relevant ist würde ich nicht wissen. Wie auch immer, Prost.
Patrick Trentin
10

$# ist die Anzahl der Argumente, aber denken Sie daran, dass es in einer Funktion anders sein wird.

$#ist die Anzahl der Positionsparameter, die an das Skript, die Shell oder die Shell-Funktion übergeben werden . Dies liegt daran, dass während der Ausführung einer Shell-Funktion die Positionsparameter vorübergehend durch die Argumente der Funktion ersetzt werden . Auf diese Weise können Funktionen ihre eigenen Positionsparameter akzeptieren und verwenden.

Dieses Skript wird immer gedruckt 3, unabhängig davon, wie viele Argumente an das Skript selbst übergeben wurden, da "$#"in der Funktion fdie Anzahl der an die Funktion übergebenen Argumente erweitert wird:

#!/bin/sh

f() {
    echo "$#"
}

f a b c

Dies ist wichtig, da Code wie dieser möglicherweise nicht wie erwartet funktioniert, wenn Sie nicht mit der Funktionsweise von Positionsparametern in Shell-Funktionen vertraut sind:

#!/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, wird $#auf die Anzahl der Argumente erweitert, die an die Funktion selbst übergeben werden. In diesem Skript ist dies immer 0.

Wenn Sie eine solche Funktionalität in einer Shell-Funktion wünschen, müssen Sie stattdessen Folgendes schreiben:

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

Dies funktioniert, weil $#es außerhalb der Funktion erweitert und als einer seiner Positionsparameter an die Funktion übergeben wird. Wird in der Funktion $1zum ersten Positionsparameter erweitert, der an die Shell-Funktion und nicht an das Skript übergeben wurde, zu dem sie gehört.

So wie $#die speziellen Parameter $1, $2usw., sowie $@und $*auch an die an eine Funktion übergebenen Argumente beziehen sich , wenn sie in der Funktion erweitert werden. Es $0ändert sich jedoch nichts an dem Namen der Funktion, weshalb ich sie immer noch verwenden konnte, um eine Qualitätsfehlermeldung zu erzeugen.

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

Wenn Sie eine Funktion in einer anderen definieren, arbeiten Sie auf ähnliche Weise mit den Positionsparametern, die an die innerste Funktion übergeben werden, in der die Erweiterung ausgeführt wird:

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

Ich habe dieses Skript aufgerufen nestedund (nach dem Ausführen chmod +x nested) ausgeführt:

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

Ja, ich weiß. "1 Argumente" ist ein Pluralisierungsfehler.

Die Positionsparameter können auch geändert werden.

Wenn Sie ein Skript schreiben, sind die Positionsparameter außerhalb einer Funktion die Befehlszeilenargumente, die an das Skript übergeben werden, sofern Sie sie nicht geändert haben .

Eine gebräuchliche Möglichkeit, sie zu ändern, ist die shiftVerwendung des eingebauten Parameters, der jeden Positionsparameter um eins nach links verschiebt, den ersten um eins senkt und um eins verringert $#:

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

Sie können auch mit dem seteingebauten geändert werden :

#!/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
quelle
1
Ein dickes Lob für die pädagogische Antwort, die über das hinausging, was gefragt wurde, aber auf die Lernabsichten des ursprünglichen Fragestellers und anderer wie mir reagierte!
Ricardo