Mais do que qualquer outra língua que conheço, "aprendi" o Bash pesquisando no Google sempre que preciso de alguma coisa. Consequentemente, posso remendar pequenos scripts que parecem funcionar. No entanto, não sei realmente o que está acontecendo e esperava uma introdução mais formal ao Bash como linguagem de programação. Por exemplo: Qual é a ordem de avaliação? quais são as regras de escopo? Qual é a disciplina de digitação, por exemplo, tudo é uma string? Qual é o estado do programa - é uma atribuição de valor-chave de strings para nomes de variáveis; há mais do que isso, por exemplo, a pilha? Existe uma pilha? E assim por diante.
Pensei em consultar o manual do GNU Bash para esse tipo de ideia, mas não parece ser o que desejo; é mais uma lista de roupas sujas de açúcar sintático do que uma explicação do modelo semântico central. Os milhões e um "tutoriais de bash" online são apenas piores. Talvez eu deva estudar primeirosh
e entender o Bash como um açúcar sintático em cima disso? Não sei se este é um modelo preciso, no entanto.
Alguma sugestão?
EDIT: Fui solicitado a fornecer exemplos do que idealmente estou procurando. Um exemplo bastante extremo do que eu consideraria uma "semântica formal" é este artigo sobre "a essência do JavaScript" . Talvez um exemplo um pouco menos formal seja o relatório Haskell 2010 .
fonte
Respostas:
Um shell é uma interface para o sistema operacional. Geralmente é uma linguagem de programação mais ou menos robusta por si só, mas com recursos projetados para facilitar a interação específica com o sistema operacional e o sistema de arquivos. A semântica do shell POSIX (doravante referida apenas como "o shell") é um pouco confusa, combinando alguns recursos de LISP (expressões s têm muito em comum com a divisão de palavras do shell ) e C (grande parte da sintaxe aritmética do shell semântica vem de C).
A outra raiz da sintaxe do shell vem de sua criação como uma mistura de utilitários UNIX individuais. A maior parte do que costuma ser embutido no shell pode, na verdade, ser implementado como comandos externos. Muitos neófitos de shell ficam confusos quando percebem que
/bin/[
existe em muitos sistemas.$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]` t
wat?
Isso faz muito mais sentido se você observar como um shell é implementado. Aqui está uma implementação que fiz como exercício. Está em Python, mas espero que não seja um problema para ninguém. Não é muito robusto, mas é instrutivo:
#!/usr/bin/env python from __future__ import print_function import os, sys '''Hacky barebones shell.''' try: input=raw_input except NameError: pass def main(): while True: cmd = input('prompt> ') args = cmd.split() if not args: continue cpid = os.fork() if cpid == 0: # We're in a child process os.execl(args[0], *args) else: os.waitpid(cpid, 0) if __name__ == '__main__': main()
Espero que o exposto acima deixe claro que o modelo de execução de um shell é basicamente:
1. Expand words. 2. Assume the first word is a command. 3. Execute that command with the following words as arguments.
Expansão, resolução de comando, execução. Todas as semânticas do shell estão vinculadas a uma dessas três coisas, embora sejam muito mais ricas do que a implementação que escrevi acima.
Nem todos os comandos
fork
. Na verdade, há um punhado de comandos que não fazem muito sentido implementados como externos (de forma que eles teriam quefork
), mas mesmo aqueles geralmente estão disponíveis como externos para conformidade estrita com POSIX.O Bash se baseia nessa base adicionando novos recursos e palavras-chave para aprimorar o shell POSIX. É quase compatível com sh, e o bash é tão onipresente que alguns autores de script passam anos sem perceber que um script pode não funcionar em um sistema POSIXly estrito. (Eu também me pergunto como as pessoas podem se importar tanto com a semântica e o estilo de uma linguagem de programação, e tão pouco com a semântica e o estilo do shell, mas eu discordo.)
Ordem de avaliação
Esta é uma questão um pouco capciosa: o Bash interpreta expressões em sua sintaxe primária da esquerda para a direita, mas em sua sintaxe aritmética segue a precedência C. No entanto, as expressões diferem das expansões . Na
EXPANSION
seção do manual do bash:Se você entende divisão de palavras, expansão de nome de caminho e expansão de parâmetro, você está no caminho certo para entender a maior parte do que o bash faz. Observe que a expansão do nome do caminho após a divisão de palavras é crítica, pois garante que um arquivo com espaço em branco no nome ainda possa ser correspondido por um glob. É por isso que o bom uso de expansões glob é melhor do que analisar comandos , em geral.
Escopo
Escopo de função
Muito parecido com o ECMAscript antigo, o shell tem escopo dinâmico, a menos que você declare explicitamente os nomes dentro de uma função.
$ foo() { echo $x; } $ bar() { local x; echo $x; } $ foo $ bar $ x=123 $ foo 123 $ bar $ …
Ambiente e "escopo" de processo
Os subshells herdam as variáveis de seus shells pais, mas outros tipos de processos não herdam nomes não exportados.
$ x=123 $ ( echo $x ) 123 $ bash -c 'echo $x' $ export x $ bash -c 'echo $x' 123 $ y=123 bash -c 'echo $y' # another way to transiently export a name 123
Você pode combinar estas regras de escopo:
$ foo() { > local -x bar=123 # Export foo, but only in this scope > bash -c 'echo $bar' > } $ foo 123 $ echo $bar $
Disciplina de digitação
Hum, tipos. Sim. Bash realmente não tem tipos, e tudo se expande para uma string (ou talvez uma palavra seja mais apropriada). Mas vamos examinar os diferentes tipos de expansões.
Cordas
Quase tudo pode ser tratado como uma string. Barewords em bash são strings cujo significado depende inteiramente da expansão aplicada a ele.
Sem expansãoPode valer a pena demonstrar que uma palavra simples é realmente apenas uma palavra e que as aspas não mudam nada a respeito.
Expansão de substring$ echo foo foo $ 'echo' foo foo $ "echo" foo foo
$ fail='echoes' $ set -x # So we can see what's going on $ "${fail:0:-2}" Hello World + echo Hello World Hello World
Para mais informações sobre expansões, leia a
Parameter Expansion
seção do manual. É muito poderoso.Expressões inteiras e aritméticas
Você pode imbuir nomes com o atributo integer para dizer ao shell para tratar o lado direito das expressões de atribuição como aritmética. Então, quando o parâmetro se expande, ele será avaliado como matemática inteira antes de expandir para ... uma string.
$ foo=10+10 $ echo $foo 10+10 $ declare -i foo $ foo=$foo # Must re-evaluate the assignment $ echo $foo 20 $ echo "${foo:0:1}" # Still just a string 2
Arrays
Argumentos e parâmetros posicionaisAntes de falar sobre arrays, pode valer a pena discutir os parâmetros posicionais. Os argumentos para um script shell pode ser acessado usando parâmetros numerados,
$1
,$2
,$3
, etc. Você pode acessar todos estes parâmetros ao mesmo tempo usando"$@"
, que a expansão tem muitas coisas em comum com matrizes. Você pode definir e alterar os parâmetros de posição usando oset
oushift
builtins, ou simplesmente invocando o shell ou uma função shell com estes parâmetros:$ bash -c 'for ((i=1;i<=$#;i++)); do > printf "\$%d => %s\n" "$i" "${@:i:1}" > done' -- foo bar baz $1 => foo $2 => bar $3 => baz $ showpp() { > local i > for ((i=1;i<=$#;i++)); do > printf '$%d => %s\n' "$i" "${@:i:1}" > done > } $ showpp foo bar baz $1 => foo $2 => bar $3 => baz $ showshift() { > shift 3 > showpp "$@" > } $ showshift foo bar baz biz quux xyzzy $1 => biz $2 => quux $3 => xyzzy
O manual do bash também às vezes se refere a
Arrays$0
um parâmetro posicional. Acho isso confuso, porque não inclui na contagem de argumentos$#
, mas é um parâmetro numerado, então meh.$0
é o nome do shell ou o script de shell atual.A sintaxe dos arrays é modelada a partir de parâmetros posicionais, portanto, é mais saudável pensar nos arrays como um tipo de nome de "parâmetros posicionais externos", se quiser. Os arrays podem ser declarados usando as seguintes abordagens:
Você pode acessar os elementos da matriz por índice:
$ echo "${foo[1]}" element1
Você pode dividir matrizes:
$ printf '"%s"\n' "${foo[@]:1}" "element1" "element2"
Se você tratar uma matriz como um parâmetro normal, obterá o índice zero.
$ echo "$baz" element0 $ echo "$bar" # Even if the zeroth index isn't set $ …
Se você usar aspas ou barras invertidas para evitar a divisão de palavras, a matriz manterá a divisão de palavras especificada:
$ foo=( 'elementa b c' 'd e f' ) $ echo "${#foo[@]}" 2
A principal diferença entre matrizes e parâmetros posicionais são:
$12
estiver definido, você pode ter certeza de que também$11
está definido. (Pode ser definido como uma string vazia, mas$#
não será menor que 12.) Se"${arr[12]}"
for definido, não há garantia de que"${arr[11]}"
esteja definido e o comprimento da matriz pode ser tão pequeno quanto 1.shift
um array, você precisa dividir e reatribuí-lo, comoarr=( "${arr[@]:1}" )
. Você também pode fazerunset arr[0]
, mas isso tornaria o primeiro elemento no índice 1.Muitas vezes, é conveniente usar expansões de nome de caminho para criar matrizes de nomes de arquivos:
$ dirs=( */ )
Comandos
Os comandos são essenciais, mas também são abordados em mais detalhes do que o manual. Leia a
SHELL GRAMMAR
seção. Os diferentes tipos de comandos são:$ startx
)$ yes | make config
) (lol)$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)Modelo de Execução
O modelo de execução, é claro, envolve um heap e uma pilha. Isso é endêmico para todos os programas UNIX. O Bash também tem uma pilha de chamadas para funções do shell, visível por meio do uso aninhado do
caller
integrado.Referências:
SHELL GRAMMAR
seção do manual do bashPor favor, faça comentários se quiser expandir mais em uma direção específica.
fonte
yes | make config
;-) Mas, falando sério, um ótimo artigo./bin/[
e/bin/test
geralmente é a mesma aplicação 2) "Suponha que a primeira palavra é um comando." - espere quando você fizer a atribuição ...execle
e interpolar as primeiras palavras no ambiente, mas isso ainda tornaria tudo um pouco mais complicado.a = 1
não trabalhar).A resposta à sua pergunta "Qual é a disciplina de digitação, por exemplo, é tudo uma string" Variáveis Bash são strings de caracteres. Mas o Bash permite operações aritméticas e comparações em variáveis quando as variáveis são inteiras. A exceção à regra as variáveis Bash são cadeias de caracteres é quando as referidas variáveis são compostas ou declaradas de outra forma
$ A=10/2 $ echo "A = $A" # Variable A acting like a String. A = 10/2 $ B=1 $ let B="$B+1" # Let is internal to bash. $ echo "B = $B" # One is added to B was Behaving as an integer. B = 2 $ A=1024 # A Defaults to string $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ echo "B = $B" # $B STRING is a string B = 10STRING01 $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ declare -i B $ echo "B = $B" # Declaring a variable with non-integers in it doesn't change the contents. B = 10STRING01 $ B=${B/STRING01/24} # Substitute "STRING01" with "24". $ echo "B = $B" B = 1024 $ declare -i B=10/2 # Declare B and assigning it an integer value $ echo "B = $B" # Variable B behaving as an Integer B = 5
Declare os significados das opções:
fonte
A página de manual do bash tem um pouco mais de informações do que a maioria das páginas de manual e inclui algumas das coisas que você está pedindo. Minha suposição, depois de mais de uma década de bash de script, é que, devido à sua 'história como uma extensão do sh, ele tem uma sintaxe funky (para manter compatibilidade com versões anteriores do sh).
FWIW, minha experiência tem sido como a sua; embora os vários livros (por exemplo, O'Reilly "Learning the Bash Shell" e similares) ajudem com a sintaxe, existem muitas maneiras estranhas de resolver vários problemas, e alguns deles não estão no livro e devem ser pesquisados no Google.
fonte