92 votes

Une sémantique pour les scripts Bash?

Plus que toute autre langue, je sais, j'ai "appris" Bash sur Google à chaque fois que j'ai besoin d'un petit quelque chose. Par conséquent, je peux patchwork ensemble de petits scripts qui apparaissent au travail. Cependant, je n'ai pas vraiment savoir ce qui se passe, et j'espérais pour une introduction formelle de Bash est un langage de programmation. Par exemple: qu'est-Ce que l'ordre d'évaluation? quelles sont les règles de portée? Qu'est-ce que la saisie de la discipline, par exemple, est tout une chaîne de caractères? Quel est l'état du programme, est une clé de la valeur de cession des chaînes de caractères pour les noms de variables; est-il plus que cela, par exemple, la pile? Est-il un tas de? Et ainsi de suite.

J'ai pensé à consulter la GNU Bash manuel de ce genre d'information, mais il ne semble pas être ce que je veux; c'est plus une liste de blanchisserie de sucre syntaxique plutôt que d'une explication de la base de modèle sémantique. Les millions de dollars-et-un "bash" tutoriels en ligne sont uniquement pour le pire. Peut-être que je devrais d'abord de l'étude sh, et de comprendre Bash est un sucre syntaxique sur le dessus de cela? Je ne sais pas si c'est un modèle précis.

Toutes les suggestions?

EDIT: j'ai été invités à fournir des exemples de ce qu'idéalement, je suis à la recherche pour. Plutôt un exemple extrême de ce que je considère être une "sémantique formelle" est ce document sur "l'essence de JavaScript". Peut-être un peu moins formelle, en est l'exemple Haskell rapport de 2010.

109voto

kojiro Points 24374

Un shell est une interface pour le système d'exploitation. Il est généralement plus ou moins robuste langage de programmation dans son propre droit, mais avec des fonctionnalités conçues pour rendre plus facile à interagir spécifiquement avec le système d'exploitation et système de fichiers. Le shell POSIX (ci-après désignée comme "la coquille") de la sémantique sont un peu d'un chien, la combinaison de certaines caractéristiques de LISP (s-expressions ont beaucoup en commun avec la coquille de couper un mot) et C (une grande partie de la coquille de l' arithmétique de la syntaxe à la sémantique vient de C).

L'autre racine de la coque de la syntaxe vient de son éducation comme un méli-mélo de personne utilitaires UNIX. La plupart de ce que sont souvent les builtins dans le shell peut effectivement être mis en œuvre comme des commandes externes. Il génère de nombreuses shell néophytes pour une boucle quand ils se rendent compte qu' /bin/[ existe sur de nombreux systèmes.

$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]`
t

wat?

Cela fait beaucoup plus de sens que si vous regardez la façon dont un shell est mis en œuvre. Voici une implémentation j'ai fait comme un exercice. C'est en Python, mais j'espère que c'est pas un raccrochage pour n'importe qui. Il n'est pas très solide, mais il est instructif:

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

J'espère que le ci-dessus indique clairement que le modèle d'exécution d'un shell est à peu près:

1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.

L'Expansion, la résolution de commande, à l'exécution. Tous les de la coque sémantique est liée à l'une de ces trois choses, même si ils sont bien plus riches que la mise en œuvre que j'ai écrit ci-dessus.

Pas toutes les commandes fork. En fait, il ya une poignée de commandes qui ne font pas une tonne de sens mis en œuvre en tant que candidat (tels qu'ils devraient l' fork), mais même ceux qui sont le plus souvent disponibles en tant que candidat pour la stricte conformité POSIX.

Bash s'appuie sur cette base par l'ajout de nouvelles fonctionnalités et de nouveaux mots-clés pour améliorer le shell POSIX. Il est presque compatible avec sh, bash, et est omniprésent, à tel point que certains auteurs de script de rester des années sans se rendre compte qu'un script peut ne pas fonctionner sur un POSIXly système strict. (Je me demande comment les gens peuvent s'inquiéter autant sur la sémantique et le style d'un langage de programmation, et si peu pour la sémantique et le style de la coque, mais je divergent.)

Ordre d'évaluation

C'est un peu une question piège: Bash interprète les expressions dans son primaires de la syntaxe, de gauche à droite, mais dans son arithmétique syntaxe qu'il suit C priorité. Les Expressions diffèrent d' expansions. De la EXPANSION de la section du manuel de bash:

L'ordre des extensions: attelle d'extension; tilde expansion, paramètre et l'expansion des variables, l'expansion arithmétique, et de la substitution de commande (fait dans une de gauche à droite de la mode); couper un mot; et le nom de chemin d'expansion.

Si vous comprenez wordsplitting, chemin d'expansion et de paramètre d'expansion, vous êtes bien sur votre chemin à la compréhension de la plupart de ce que bash n'. Notez que le nom de chemin d'expansion à venir après wordsplitting est essentielle, car elle assure un fichier avec des espaces dans son nom peut encore être compensée par une boule. C'est pourquoi une bonne utilisation de glob expansions est mieux que de l'analyse des commandes, en général.

Portée

Portée de la fonction

Un peu comme les vieux ECMAscript, la coquille est dynamique portée, sauf si vous explicitement déclarer les noms à l'intérieur d'une fonction.

$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo

$ bar

$ x=123
$ foo
123
$ bar

$ …

L'environnement et le processus de "portée"

Sous-coquille hériter les variables de leurs parents coquilles, mais d'autres types de processus n'héritent pas désexporter noms.

$ 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

Vous pouvez combiner ces règles de portée:

$ foo() {
>   local -x bar=123 # Export foo, but only in this scope
>   bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar

$

En tapant la discipline

Euh, types. Ouais. Bash n'est pas vraiment des types, et tout se développe à une chaîne de caractères (ou peut-être un mot serait plus approprié.) Mais, nous allons examiner les différents types d'extensions.

Les chaînes

À peu près tout peut être traité comme une chaîne. Barewords en bash sont des chaînes de caractères dont le sens dépend entièrement de l'expansion appliquée.

Pas d'expansion

Il peut être intéressant de démontrer qu'un simple mot est vraiment juste un mot, et que des citations de ne rien changer à ce sujet.

$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo

Sous-chaîne d'extension

$ fail='echoes'
$ set -x # So we can see what's going on
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World

Pour en savoir plus sur les expansions, lire l' Parameter Expansion section du manuel. Il est très puissant.

Les entiers et les expressions arithmétiques

Vous pouvez imprégner les noms avec l'attribut entier pour indiquer au shell pour traiter la partie droite de l'affectation des expressions arithmétiques. Puis, lorsque le paramètre s'étend, elle sera évaluée comme math entier avant de s'étendre à ... une chaîne de caractères.

$ 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

Les tableaux

Les Arguments et les Paramètres Positionnels

Avant de parler de tableaux, il pourrait être intéressant de discuter des paramètres positionnels. Les arguments d'un script shell peut être consulté à l'aide de paramètres numérotés, $1, $2, $3, etc. Vous pouvez accéder à tous ces paramètres à la fois à l'aide de "$@", dont l'expansion a beaucoup de choses en commun avec les tableaux. Vous pouvez définir et modifier les paramètres positionnels à l'aide de l' set ou shift objets internes, ou simplement en invoquant la coquille ou d'une fonction shell avec ces paramètres:

$ 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

Le manuel de bash aussi parfois se réfère $0 comme un paramètre de position. Je trouve cela déroutant, car il n'a pas l'inclure dans l'argument count $#, mais c'est un paramètre numéroté, donc, meh. $0 est le nom de la coque ou le courant d'un script shell.

Les tableaux

La syntaxe des tableaux est modélisé d'après les paramètres positionnels, donc c'est surtout sain de penser à des tableaux d'une sorte de "externes des paramètres positionnels", si vous le souhaitez. Les tableaux peuvent être déclarées en utilisant l'une des méthodes suivantes:

$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )

Vous pouvez accéder aux éléments d'un tableau par index:

$ echo "${foo[1]}"
element1

Vous pouvez découper les tableaux:

$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"

Si vous traitez un tableau comme une normale de paramètre, vous obtiendrez le zéro de l'index.

$ echo "$baz"
element0
$ echo "$bar" # Even if the zeroth index isn't set

$ …

Si vous utilisez des guillemets ou des barres obliques inverses pour éviter wordsplitting, le tableau sera de maintenir l'spécifiée wordsplitting:

$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2

La principale différence entre les tableaux et les paramètres positionnels sont:

  1. Les paramètres positionnels sont pas rares. Si $12 est défini, vous pouvez en être sûr, $11 est, trop. (Il pourrait être fixé à la chaîne vide, mais $# ne seront pas moins de 12.) Si "${arr[12]}" est réglé, il n'y a aucune garantie que l' "${arr[11]}" est défini, et la longueur du tableau pourrait être aussi petit que 1.
  2. Le zéro de l'élément d'un tableau est sans ambiguïté le zéro élément de ce tableau. Dans les paramètres positionnels, le zéro de l'élément n'est pas le premier argument, mais le nom de la coquille ou d'un script shell.
  3. D' shift un tableau, vous avez de trancher et de le réaffecter, comme arr=( "${arr[@]:1}" ). Vous pouvez également faire de unset arr[0], mais qui ferait le premier élément à l'indice 1.
  4. Les tableaux peuvent être partagées implicitement entre shell fonctions globales, mais vous devez transmettre explicitement position de paramètres à une fonction shell pour voir ceux.

Il est souvent plus pratique d'utiliser le nom de chemin des expansions de créer des tableaux de noms de fichiers:

$ dirs=( */ )

Les commandes

Les commandes sont essentiels, mais ils sont également couverts dans une meilleure profondeur que je peux par le manuel. Lire l' SHELL GRAMMAR section. Les différents types de commandes sont les suivantes:

  1. Des Commandes simples (par exemple, $ startx)
  2. Pipelines (par exemple, $ yes | make config) (lol)
  3. Les listes (par exemple, $ grep -qF foo file && sed 's/foo/bar/' file > newfile)
  4. Composé de Commandes (par exemple, $ ( cd -P /var/www/webroot && echo "webroot is $PWD" ))
  5. Coprocesses (Complexe, pas exemple)
  6. Fonctions (Un nom composé de commande qui peut être traitée comme une simple commande)

Modèle D'Exécution

Le modèle d'exécution de cours implique à la fois un segment de mémoire et une pile. Cette espèce est endémique de tous les programmes UNIX. Bash a aussi une pile des appels de fonctions d'interpréteur de commandes, visible via imbriquée à l'utilisation de l' caller builtin.

Références:

  1. L' SHELL GRAMMAR de la section du manuel de bash
  2. Le XCU Shell Langage de Commande de la documentation
  3. Le Bash Guide sur Greycat du wiki.
  4. Advanced Programming in the UNIX Environment

Veuillez faire des commentaires, si vous voulez m'étendre dans une direction spécifique.

5voto

Keith Reynolds Points 485

La réponse à votre question "qu'est-Ce que la saisie de la discipline, par exemple, est tout une chaîne de caractères" Variables Bash sont des chaînes de caractères. Mais, Bash permet les opérations arithmétiques et les comparaisons de variables lorsque les variables sont des entiers. L'exception à la règle des variables Bash sont des chaînes de caractères est lorsque les variables sont affichés ou déclaration contraire

$ 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

Déclarer option significations:

  • -une Variable est un tableau.
  • -f Utiliser des noms de fonction.
  • -je La variable est d'être traité comme un entier; l'arithmétique évaluation est effectuée lorsque la variable est affectée une valeur.
  • -p affiche les attributs et les valeurs de chaque variable. Quand -p est utilisé, des options supplémentaires sont ignorés.
  • -r Rendre les variables en lecture seule. Ces variables ne peuvent pas être affectés de valeurs par la suite des instructions d'affectation, ils ne peuvent être annuler.
  • -t Donner à chaque variable de la trace de l'attribut.
  • -x Marque de chaque variable à l'exportation pour les commandes ultérieures via l'environnement.

1voto

philwalk Points 81

La page de manuel de bash a un peu plus d'info que la plupart des pages de manuel, et comprend une partie de ce que vous me demandez. Mon hypothèse, après plus d'une décennie de scripting bash, c'est que, en raison de son histoire, comme une extension de sh, il a quelques funky syntaxe (pour assurer la compatibilité descendante avec sh).

FWIW, mon expérience a été comme la vôtre; bien que les divers livres (par exemple, O'Reilly, "l'Apprentissage de la Shell Bash" et similaires) ne l'aide à la syntaxe, il y a beaucoup de façons étranges de la résolution de divers problèmes, et certains d'entre eux ne sont pas dans le livre et doit être googlé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X