O que $ # significa em bash?


26

Eu tenho um script em um arquivo chamado instance:

echo "hello world"
echo ${1}

E quando eu executo esse script usando:

./instance solfish

Eu recebo esta saída:

hello world
solfish

Mas quando eu corro:

echo $# 

Diz "0". Por quê? Eu não entendo o que $#significa.

Por favor, explique.


10
Por que você corre $#? O que você quer alcançar? Onde você conseguiu esse comando. Não é de todo relevante.
precisa saber é o seguinte

7
@ Pilot6, nesse caso, ele pede o significado de uma variável; esse não é um caso específico; por que o op precisaria fornecer essas informações?
ADDB

21
O @solfish Pilot está tentando entender o que você estava esperando. Você disse que correu echo $#e voltou, o 0que é normal. Você ficou surpreso com isso, mas não explica o que estava esperando ou por que ficou surpreso. Isso nos ajudaria a dar uma resposta melhor se você explicasse o que estava esperando. O que você pensou que echo $#faria. Se você executasse ./instance solfishe ./instancecontivesse echo $#, isso seria impresso 1e não 0.
terdon


1
Java também não usa argc.
JakeRobb

Respostas:


66

$#é uma variável especial em bash, que se expande para o número de argumentos (parâmetros posicionais), ou seja, $1, $2 ...passados ​​para o script em questão ou para o shell em caso de argumento diretamente transmitido para o shell, por exemplo, em bash -c '...' .....

Isso é semelhante ao argcC.


Talvez isso deixe claro:

$ bash -c 'echo $#'
0

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

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

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

Observe que, bash -crecebe um argumento após o comando que o segue a partir de 0 ( $0; tecnicamente, é apenas bashuma maneira de deixar você definir $0, não um argumento realmente), então _é usado aqui apenas como um espaço reservado; argumentos reais são x( $1), y( $2) e z( $3).


Da mesma forma, no seu script (supondo script.sh) se você tiver:

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

Então, quando você faz:

./script.sh foo bar

o script produzirá 2; Da mesma forma,

./script.sh foo

produzirá 1.


1
Eu acho que essa resposta é enganosa. $ # é o índice máximo da matriz de argumentos. Se você der um argumento, ele estará disponível em $ 0 e $ # terá o valor 0. Se você der dois argumentos, eles estarão em $ 0 e $ 1 e $ # terão o valor 1.
JakeRobb

1
Além disso, quando você usa bash -c, o comportamento é diferente do que executar um script de shell executável, porque neste último caso o argumento com índice 0 é o comando do shell usado para invocá-lo. Como tal, acho que a maneira de corrigir essa resposta é alterá-la para executar scripts como arquivos, em vez de usar bash -c, pois é assim que o solicitante estava fazendo isso.
JakeRobb

1
@JakeRobb Não. Para um script, $0seria o próprio script; argumentos seria $1, $2, $3.... bash -co comportamento é diferente, uma vez que se destina ao uso não interativo e os argumentos a seguir ao comando começariam $0, mencionei isso claramente.
heemayl

5
@JakeRobb Você me ouviu mal. Se você der um argumento, ele estará disponível em $ 0 e $ # terá o valor 0 - isso está errado.
heemayl

2
Se você colocar um programa completo que analise args bash -c, deverá passar bashum nome significativo como preenchedor para o 0º arg. bash -c 'echo "$@"' foo barapenas imprime bar, porque "$@"não inclui $0, o mesmo de sempre. bash -cnão "transforma $ 0 em um dos argumentos", apenas permite que você defina "$ 0" da mesma maneira que executa um programa com a execvechamada do sistema ou qualquer maneira de substituir o 0º argumento. $0nunca deve ser considerado "um dos argumentos".
Peter Cordes

17

echo $# gera o número de parâmetros posicionais do seu script.

Você não tem nenhum, então gera 0.

echo $# é útil dentro do script, não como um comando separado.

Se você executar um script com alguns parâmetros como

./instance par1 par2

o echo $#colocado no script produzirá 2.


6
Para o exemplo do OP: Se você adicionar a linha echo $#para o script e que execute novamente ./instance solfishvocê deve obter uma linha de saída adicional com 1uma vez que forneceu um parâmetro
derHugo

14

$#é normalmente usado em scripts bash para garantir que um parâmetro seja passado. Geralmente, você verifica um parâmetro no início do seu script.

Por exemplo, aqui está um trecho de script em que eu estava trabalhando hoje:

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

Para resumir os $#relatórios, o número de parâmetros passados ​​para um script. No seu caso, você não passou parâmetros e o resultado relatado é 0.


Outros #usos em Bash

A #é frequentemente usado em festa para contar o número de ocorrências ou o comprimento de uma variável.

Para encontrar o comprimento de uma sequência:

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

retorna: 11

Para encontrar o número de elementos da matriz:

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

retorna: 3

Para encontrar o comprimento do primeiro elemento da matriz:

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

retorna: 1(O comprimento de A0 é o primeiro elemento, pois as matrizes usam índices / subscritos baseados em zero).


$# -ne 1? Por que não $# -le 0ou equivalente?
Patrick Trentin

@PatrickTrentin Porque não quero que eles passem dois ou mais parâmetros. Como o capitão disse em "Outubro Vermelho": "Apenas um".
WinEunuuchs2Unix

Então: "exatamente um argumento necessário ..." na mensagem de erro
Patrick Trentin

@PatrickTrentin A mensagem de erro explica como o script é usado com um exemplo do uso de um argumento. Além disso, o script de backup é chamado por cron.daily que só é configurado uma vez por savvy alguém tecnologia: askubuntu.com/questions/917562/...
WinEunuuchs2Unix

Como isso é relevante, eu não saberia. Enfim, um brinde.
22817 Patrick Trentin

10

$# é o número de argumentos, mas lembre-se de que será diferente em uma função.

$#é o número de parâmetros posicionais passados ​​para o script, shell ou função de shell . Isso ocorre porque, enquanto uma função shell está em execução, os parâmetros posicionais são substituídos temporariamente pelos argumentos da função . Isso permite que as funções aceitem e usem seus próprios parâmetros posicionais.

Esse script sempre imprime 3, independentemente de quantos argumentos foram passados ​​para o próprio script, porque "$#"na função se fexpande para o número de argumentos passados ​​para a função:

#!/bin/sh

f() {
    echo "$#"
}

f a b c

Isso é importante porque significa que códigos como este não funcionam como você poderia esperar, se você não estiver familiarizado com o funcionamento dos parâmetros posicionais nas funções do 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...

Em check_args, $#expande para o número de argumentos passados ​​para a própria função, que nesse script é sempre 0.

Se você quiser essa funcionalidade em uma função shell, precisará escrever algo assim:

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

Isso funciona porque $#é expandido fora da função e passado para a função como um de seus parâmetros posicionais. Dentro da função, $1expande para o primeiro parâmetro posicional que foi passado para a função shell, em vez de para o script do qual faz parte.

Assim como $#, os parâmetros especiais $1, $2etc., bem como $@e $*, também dizem respeito aos argumentos passados para uma função, quando são expandidas na função. No entanto, $0se não mudar para o nome da função, que é por isso que eu ainda era capaz de usá-lo para produzir uma mensagem de erro qualidade.

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

Da mesma forma, se você definir uma função dentro de outra, estará trabalhando com os parâmetros posicionais passados ​​para a função mais interna na qual a expansão é executada:

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

Chamei esse script nestede (após a execução chmod +x nested) executei:

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

Sim eu conheço. "1 argumentos" é um erro de pluralização.

Os parâmetros posicionais também podem ser alterados.

Se você estiver escrevendo um script, os parâmetros posicionais fora de uma função serão os argumentos da linha de comando passados ​​para o script, a menos que você os tenha alterado .

Uma maneira comum de alterá-los é com o shiftbuiltin, que desloca cada parâmetro posicional para a esquerda em um, descartando o primeiro e diminuindo $#em 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".

Eles também podem ser alterados com o setbuiltin:

#!/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
Parabéns pela resposta educativa que foi além do que foi solicitado, mas respondeu às intenções de aprendizado do solicitante original e de outros como eu!
1010 Ricardo
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.