bash - programas - scripts linux ejercicios resueltos
Una semántica para scripts Bash? (3)
La página de manual de bash tiene bastante más información que la mayoría de las páginas de manual, e incluye algo de lo que estás pidiendo. Mi suposición después de más de una década de scripting bash es que, debido a su ''historia como una extensión de sh, tiene una sintaxis funky (para mantener la compatibilidad con sh).
FWIW, mi experiencia ha sido como la tuya; aunque los diversos libros (por ej., O''Reilly "Learning the Bash Shell" y similares) sí ayudan con la sintaxis, existen muchas maneras extrañas de resolver varios problemas, y algunos de ellos no están en el libro y deben ser buscados en Google.
Más que cualquier otro idioma que conozco, he "aprendido" Bash buscando en Google cada vez que necesito algo pequeño. En consecuencia, puedo mosaico juntos pequeños scripts que parecen funcionar. Sin embargo, realmente no sé lo que está pasando, y esperaba una introducción más formal a Bash como lenguaje de programación. Por ejemplo: ¿Cuál es el orden de evaluación? ¿Cuáles son las reglas de alcance? ¿Cuál es la disciplina de tipeo, por ejemplo, es todo una cadena? ¿Cuál es el estado del programa? ¿Es una asignación de valores clave de cadenas a nombres de variables? ¿Hay más que eso, por ejemplo, la pila? ¿Hay un montón? Y así.
Pensé en consultar el manual de GNU Bash para este tipo de información, pero no parece ser lo que quiero; es más una lista de lavandería de azúcar sintáctico que una explicación del modelo semántico central. Los "tutoriales bash" de millones y uno en línea son solo peores. Tal vez debería estudiar primero sh
, y entender Bash como un azúcar sintáctico en la parte superior de esto? Sin embargo, no sé si este es un modelo preciso.
¿Alguna sugerencia?
EDITAR: Me han pedido que proporcione ejemplos de lo que idealmente estoy buscando. Un ejemplo bastante extremo de lo que yo consideraría una "semántica formal" es este artículo sobre "la esencia de JavaScript" . Tal vez un ejemplo un poco menos formal es el informe Haskell 2010 .
La respuesta a su pregunta "¿Cuál es la disciplina de tipeo, por ejemplo, todo es una cadena?" Las variables de Bash son cadenas de caracteres. Pero, Bash permite operaciones aritméticas y comparaciones en variables cuando las variables son números enteros. La excepción para gobernar las variables de Bash son las cadenas de caracteres cuando esas variables se escriben o se escriben de otro modo
$ 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
Declarar los significados de las opciones:
- - Una variable es una matriz.
- -f Usa solo nombres de funciones.
- -i La variable debe tratarse como un entero; la evaluación aritmética se realiza cuando a la variable se le asigna un valor.
- -p Muestra los atributos y valores de cada variable. Cuando se usa -p, las opciones adicionales son ignoradas.
- -r Hacer variables de solo lectura. A estas variables no se les pueden asignar valores por declaraciones de asignación posteriores, ni tampoco se pueden deshacer.
- -t Dale a cada variable el atributo de traza.
- -x Marque cada variable para exportar a comandos posteriores a través del entorno.
Un shell es una interfaz para el sistema operativo. Por lo general, es un lenguaje de programación más o menos robusto en sí mismo, pero con funciones diseñadas para facilitar la interacción específica con el sistema operativo y el sistema de archivos. La semántica del shell POSIX (en lo sucesivo, "el shell") es un poco mutt, combinando algunas características de LISP (las s expresiones tienen mucho en común con la división de palabras de shell) y C (gran parte de la sintaxis aritmética de la shell) la semántica proviene de C).
La otra raíz de la sintaxis del shell proviene de su educación como una mezcolanza de utilidades individuales de UNIX. La mayoría de lo que a menudo están integrados en el shell pueden implementarse como comandos externos. Lanza muchos neófitos shell para un ciclo cuando se dan cuenta de que /bin/[
existe en muchos sistemas.
$ if ''/bin/['' -f ''/bin/[''; then echo t; fi # Tested as-is on OS X, without the `]`
t
wat?
Esto tiene mucho más sentido si observa cómo se implementa un shell. Aquí hay una implementación que hice como ejercicio. Está en Python, pero espero que eso no afecte a nadie. No es terriblemente robusto, pero es instructivo:
#!/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 lo anterior aclare que el modelo de ejecución de un shell es más o menos:
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
Expansión, resolución de comandos, ejecución. Toda la semántica del shell está ligada a una de estas tres cosas, aunque son mucho más ricas que la implementación que escribí anteriormente.
No todos los comandos se fork
. De hecho, hay un puñado de comandos que no tienen mucho sentido implementados como externos (tales que tendrían que fork
), pero incluso aquellos a menudo están disponibles como externos para el estricto cumplimiento de POSIX.
Bash se basa en esta base al agregar nuevas características y palabras clave para mejorar el shell POSIX. Es casi compatible con sh, y bash es tan omnipresente que algunos autores de guiones pasan años sin darse cuenta de que un guión puede no funcionar realmente en un sistema estrictamente POSIX. (También me pregunto cómo las personas pueden preocuparse tanto por la semántica y el estilo de un lenguaje de programación, y tan poco por la semántica y el estilo del intérprete de comandos, pero divergen).
Orden de evaluación
Esta es una pregunta un tanto engañosa: Bash interpreta expresiones en su sintaxis primaria de izquierda a derecha, pero en su sintaxis aritmética sigue a C precedencia. Las expresiones difieren de las expansiones , sin embargo. Desde la sección EXPANSION
del manual de bash:
El orden de las expansiones es: expansión ortopédica; expansión de tilde, expansión de parámetro y variable, expansión aritmética y sustitución de comando (hecho de izquierda a derecha); división de palabras; y la expansión del nombre de ruta.
Si usted entiende la división de palabras, la expansión del nombre de la ruta de acceso y la expansión de parámetros, estará en camino de comprender la mayor parte de lo que Bash hace. Tenga en cuenta que la expansión del nombre de ruta que viene después de la división de palabras clave es crítica, ya que asegura que un archivo con espacios en blanco en su nombre todavía pueda ser asociado por una variable global. Esta es la razón por la que un buen uso de las expansiones glob es mejor que el análisis de comandos , en general.
Alcance
Alcance de la función
Al igual que el viejo ECMAscript, el shell tiene un alcance dinámico a menos que declares explícitamente nombres dentro de una función.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
Medio ambiente y proceso "alcance"
Las subcadenas heredan las variables de sus shells principales, pero otros tipos de procesos no heredan los nombres no 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
Puede combinar estas reglas de alcance:
$ foo() {
> local -x bar=123 # Export foo, but only in this scope
> bash -c ''echo $bar''
> }
$ foo
123
$ echo $bar
$
Disciplina de escribir
Um, tipos. Sí. Bash realmente no tiene tipos, y todo se expande a una cadena (o tal vez una palabra sería más apropiada). Pero examinemos los diferentes tipos de expansiones.
Instrumentos de cuerda
Casi todo puede tratarse como una cuerda. Las palabras brutas en bash son cadenas cuyo significado depende completamente de la expansión que se le aplica.
Sin expansiónPuede valer la pena demostrar que una palabra simple en realidad es solo una palabra, y que las citas no cambian nada al respecto.
$ echo foo
foo
$ ''echo'' foo
foo
$ "echo" foo
foo
Expansión de subcadenas
$ fail=''echoes''
$ set -x # So we can see what''s going on
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
Para obtener más información sobre expansiones, lea la sección Parameter Expansion
del manual. Es bastante poderoso.
Enteros y expresiones aritméticas
Puede imbuir nombres con el atributo entero para decirle al shell que trate el lado derecho de las expresiones de asignación como aritmética. Luego, cuando el parámetro se expanda, se evaluará como matemática entera antes de expandirse a ... una cadena.
$ 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 y parámetros posicionales Antes de hablar sobre las matrices, valdría la pena discutir los parámetros posicionales. Se puede acceder a los argumentos de un script de shell utilizando parámetros numerados, $1
, $2
, $3
, etc. Puede acceder a todos estos parámetros a la vez usando "$@"
, cuya expansión tiene muchas cosas en común con las matrices. Puede establecer y cambiar los parámetros posicionales utilizando el set
o shift
comandos incorporados, o simplemente invocando el shell o una función de shell con estos 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
El manual de bash también a veces se refiere a $0
como un parámetro posicional. Encuentro esto confuso, porque no lo incluye en el argumento count $#
, pero es un parámetro numerado, entonces meh. $0
es el nombre del shell o el script de shell actual.
La sintaxis de las matrices se modela después de los parámetros posicionales, por lo que es más saludable pensar en las matrices como un tipo nombrado de "parámetros posicionales externos", si lo desea. Las matrices se pueden declarar utilizando los siguientes enfoques:
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
Puede acceder a los elementos de la matriz por índice:
$ echo "${foo[1]}"
element1
Puede cortar matrices:
$ printf ''"%s"/n'' "${foo[@]:1}"
"element1"
"element2"
Si trata una matriz como un parámetro normal, obtendrá el índice zeroth.
$ echo "$baz"
element0
$ echo "$bar" # Even if the zeroth index isn''t set
$ …
Si utiliza comillas o barras diagonales inversas para evitar la división de palabras, la matriz mantendrá las divisiones de palabras especificadas:
$ foo=( ''elementa b c'' ''d e f'' )
$ echo "${#foo[@]}"
2
La principal diferencia entre matrices y parámetros posicionales es:
- Los parámetros posicionales no son escasos Si se establece
$12
, puede estar seguro de que$11
está configurado. (Podría establecerse en la cadena vacía, pero$#
no será menor que 12.) Si se establece"${arr[12]}"
, no hay garantía de que esté configurado"${arr[11]}"
, y la longitud de la matriz podría ser tan pequeña como 1. - El elemento zeroth de una matriz es inequívocamente el elemento zeroth de esa matriz. En los parámetros posicionales, el elemento zeroth no es el primer argumento , sino el nombre del script de shell o shell.
- Para
shift
una matriz, debe dividirla y reasignarla, comoarr=( "${arr[@]:1}" )
. También podríaunset arr[0]
, pero eso haría que el primer elemento en el índice 1. - Las matrices se pueden compartir implícitamente entre funciones de shell como globales, pero tiene que pasar explícitamente parámetros posicionales a una función de shell para que los vea.
A menudo es conveniente usar expansiones de nombre de ruta para crear matrices de nombres de archivos:
$ dirs=( */ )
Comandos
Los comandos son clave, pero también están cubiertos con mayor profundidad que con el manual. Lea la sección SHELL GRAMMAR
. Los diferentes tipos de comandos son:
- Comandos simples (por ejemplo,
$ startx
) - Tuberías (por ejemplo,
$ yes | make config
) (lol) - Listas (ej.
$ grep -qF foo file && sed ''s/foo/bar/'' file > newfile
) - Comandos compuestos (por ejemplo,
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
) - Coprocesos (complejo, sin ejemplo)
- Funciones (Un comando compuesto nombrado que se puede tratar como un comando simple)
Modelo de ejecución
El modelo de ejecución por supuesto involucra tanto un montón como una pila. Esto es endémico para todos los programas de UNIX. Bash también tiene una pila de llamadas para funciones de shell, visible a través del uso anidado de la función interna de la caller
.
Referencias
- La sección
SHELL GRAMMAR
del manual de bash - La documentación de XCU Shell Command Language
- La guía Bash en la wiki de Greycat.
- Programación avanzada en el entorno UNIX
Por favor, haga comentarios si quiere que me expanda más en una dirección específica.