Normalmente, cuando la gente menciona "lenguaje de scripts de shell", lo que les viene a la mente es bash, ksh, sh u otros lenguajes de scripts de Linux/Unix similares. Un lenguaje de scripts es otra forma de comunicarse con la computadora. Con una interfaz gráfica de ventanas (da igual si es Windows o Linux), el usuario puede mover el ratón y hacer clic en varios objetos, como botones, listas, casillas de verificación, etc. Pero esta forma resulta muy inconveniente cada vez que el usuario quiere que la computadora/servidor realice la misma tarea (por ejemplo, convertir fotos en lote, o descargar nuevas películas, mp3, etc.). Para que todas estas cosas sean simples y automáticas, podemos usar scripts de shell.

Algunos lenguajes de programación, como Pascal, FoxPro, C, Java, etc., necesitan ser compilados antes de ejecutarse. Requieren un compilador adecuado para que nuestro código realice una tarea.

Shell脚本编程初体验

Otros lenguajes de programación, como PHP, JavaScript, Visual Basic, etc., no necesitan compilador; por lo tanto, requieren un intérprete, y podemos ejecutar el programa sin compilar el código.

Los scripts de shell también son como un intérprete, pero normalmente se usan para invocar programas externos ya compilados. Luego, capturan la salida, el código de salida y lo procesan según el caso.

Uno de los lenguajes de scripts de shell más populares en el mundo de Linux es bash. Y creo (esta es mi opinión) que la razón es que, por defecto, el shell bash permite al usuario navegar cómodamente a través del historial de comandos (los ejecutados previamente), en contraste con ksh, que exige hacer algunos ajustes en .profile o recordar algunas combinaciones de teclas "mágicas" para consultar el historial y corregir comandos.

Bien, creo que esta introducción es suficiente; el resto, decidir qué entorno te conviene más, lo dejo a tu criterio. De ahora en adelante, solo hablaré de bash y sus scripts. En los ejemplos siguientes, usaré CentOS 6.6 y bash-4.1.2. Asegúrate de tener la misma versión o una superior.

Flujos de los scripts de Shell

El lenguaje de scripts de shell es similar a conversar con varias personas. Solo tienes que imaginar todos los comandos como personas que pueden ayudarte a hacer cosas, siempre que les pidas que lo hagan de la manera correcta. Por ejemplo, quieres escribir un documento. Primero, necesitas papel. Luego, necesitas decirle el contenido a alguien para que lo escriba por ti. Finalmente, quieres guardarlo en algún lugar. O bien, quieres construir una casa, así que necesitas contratar a las personas adecuadas para despejar el terreno. Cuando dicen "trabajo terminado", otros ingenieros pueden ayudarte a levantar las paredes. Finalmente, cuando esos ingenieros también te dicen "trabajo terminado", puedes llamar a los pintores para que decoren la casa. ¿Qué pasaría si hicieras venir a los pintores antes de que las paredes estén levantadas? Creo que empezarían a quejarse. Casi todos estos comandos similares a personas hablan; si completan el trabajo sin ningún problema, te lo dicen a la "salida estándar". Si no pueden hacer lo que les pides, te lo dicen a la "salida de error estándar". Así, al final, todos los comandos te escuchan a través de la "entrada estándar".

Ejemplo rápido: cuando abres una terminal de Linux y escribes algo de texto, estás hablando con bash a través de la "entrada estándar". Entonces, preguntémosle al shell bashwho am i (¿quién soy?).

root@localhost ~]# who am i                                <--- 你通过标准输入对 bash shell 说
root     pts/0        2015-04-22 20:17 (192.168.1.123)     <--- bash shell通过标准输出回答你

Ahora, digamos algunas preguntas que bash no entiende:

[root@localhost ~]# blablabla           <--- 哈,你又在和标准输入说话了
-bash: blablabla: command not found     <--- bash通过标准错误在发牢骚了

La primera palabra antes de ":" suele ser el comando que se queja. En realidad, cada uno de estos flujos tiene su propio número de índice (nota del traductor de LCTT: descriptor de archivo):

  • Entrada estándar (stdin) – 0
  • Salida estándar (stdout) – 1
  • Error estándar (stderr) – 2

Si realmente quieres saber qué dijo cada comando en la salida, necesitas redirigir esa "intervención" a un archivo (usando el signo mayor que ">" y el índice del flujo después del comando):

[root@localhost ~]# blablabla 1> output.txt
-bash: blablabla: command not found

En este ejemplo, intentamos redirigir el flujo 1 (stdout) al archivo llamado output.txt. Veamos qué se hizo con el contenido de ese archivo; podemos hacerlo con el comando cat:

[root@localhost ~]# cat output.txt
[root@localhost ~]#

Parece que está vacío. Bien, ahora redirijamos el flujo 2 (stderr):

[root@localhost ~]# blablabla 2> error.txt
[root@localhost ~]#

Bien, vemos que la queja desapareció. Revisemos ese archivo:

[root@localhost ~]# cat error.txt
-bash: blablabla: command not found
[root@localhost ~]#

¡Efectivamente! Vemos que todas las quejas quedaron registradas en el archivo errors.txt.

A veces, un comando produce simultáneamentestdoutystderr. Para redirigirlos a archivos diferentes, podemos usar la siguiente sentencia:

command 1>out.txt 2>err.txt

Para acortar un poco la sentencia, podemos omitir el "1", porque por defectostdoutse redirigirá:

command >out.txt 2>err.txt

Bien, intentemos hacer algo "malo". Usemos el comando rm para eliminar file1 y folder1:

[root@localhost ~]# rm -vf folder1 file1 > out.txt 2>err.txt

Ahora revisemos el siguiente archivo de salida:

[root@localhost ~]# cat out.txt
removed `file1'
[root@localhost ~]# cat err.txt
rm: cannot remove `folder1': Is a directory
[root@localhost ~]#

Como podemos ver, los diferentes flujos se separaron en archivos diferentes. A veces, esto tampoco es muy conveniente, porque queremos ver lo que sucede consecutivamente antes o después de ciertas operaciones cuando aparece un error. Para lograr esto, podemos redirigir ambos flujos al mismo archivo:

command >>out_err.txt 2>>out_err.txt

Nota: observa que usé ">>" en lugar de ">". Esto nos permite añadir al archivo, en lugar de sobrescribirlo.

También podemos redirigir un flujo a otro:

command >out_err.txt 2>&1

Déjame explicártelo. La salida estándar de todos los comandos se redirigirá a out_err.txt, la salida de error se redirigirá al flujo 1 (ya explicado arriba), y ese flujo se redirigirá al mismo archivo. Veamos este ejemplo:

[root@localhost ~]# rm -fv folder2 file2 >out_err.txt 2>&1
[root@localhost ~]# cat out_err.txt
rm: cannot remove `folder2': Is a directory
removed `file2'
[root@localhost ~]#

Viendo esta salida combinada, podemos describirla así: primero,rmel comando intenta eliminar folder2, pero no lo conseguirá, porque Linux requiere-rla opción para permitirrmque el comando elimine la carpeta; el segundo file2 sí se eliminará. Alrmproporcionar-vla opción (detalle), hacemos que el comando rm nos indique cada archivo o carpeta eliminados.

Esto es casi todo lo que necesitas saber sobre la redirección. Digo casi, porque hay una herramienta de redirección aún más importante llamada "tubería" (pipe). Mediante el símbolo | (tubería), normalmente redirigimos elstdoutflujo.

Por ejemplo, tenemos un archivo de texto como este:

[root@localhost ~]# cat text_file.txt
This line does not contain H e l l o  word
This lilne contains Hello
This also containd Hello
This one no due to HELLO all capital
Hello bash world!

Y necesitamos encontrar las líneas que contienen "Hello". En Linux hay ungrepcomando que puede hacer ese trabajo:

[root@localhost ~]# grep Hello text_file.txt
This lilne contains Hello
This also containd Hello
Hello bash world!
[root@localhost ~]#

Esto funciona muy bien cuando tenemos un archivo y queremos buscar en él. Pero, ¿y si necesitamos buscar algo en la salida de otro comando? Sí, por supuesto, podemos redirigir la salida a un archivo y luego buscar en el archivo:

[root@localhost ~]# fdisk -l>fdisk.out
[root@localhost ~]# grep "Disk /dev" fdisk.out
Disk /dev/sda: 8589 MB, 8589934592 bytes
Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes
Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes
[root@localhost ~]#

¡Y si quieres usar grep con un contenido entre comillas dobles que incluya espacios!

Nota: el comando fdisk muestra información sobre las unidades de disco del sistema operativo Linux.

Como vemos, esta forma es muy inconveniente, porque en poco tiempo ensuciamos el espacio de los archivos temporales. Para realizar esta tarea, podemos usar tuberías. Estas nos permiten redirigir elstdoutde un comando alstdinflujo de otro comando:

[root@localhost ~]# fdisk -l | grep "Disk /dev"
Disk /dev/sda: 8589 MB, 8589934592 bytes
Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes
Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes
[root@localhost ~]#

Como puedes ver, no necesitamos archivos temporales para obtener el mismo resultado. Nosotrosfdisk stdoutredirigimos la salida estándar a la entrada estándar.grep stdin。

Nota: la redirección por tubería siempre es de izquierda a derecha.

Hay algunos otros tipos de redirección, pero los dejaremos para más adelante.

Mostrar información personalizada en la shell

Como ya sabemos, normalmente la comunicación con la shell y dentro de la shell se realiza en forma de diálogo. Por lo tanto, creemos algunos scripts de verdad que también nos hablen. Esto te permitirá aprender algunos comandos simples y tener una mejor comprensión del concepto de script.

Supongamos que somos el gerente de la mesa de ayuda de una empresa y queremos crear un script de shell para registrar la información de las llamadas: número de teléfono, nombre de usuario y una breve descripción del problema. Planeamos almacenar esta información en un archivo de texto plano llamado data.txt para futuras estadísticas. El script en sí funciona de forma conversacional, lo que hará la vida un poco más fácil al personal de la mesa de ayuda. Entonces, primero necesitamos mostrar las preguntas. Para mostrar información, podemos usar los comandos echo y printf. Ambos se usan para mostrar información, pero printf es más potente porque podemos formatear bien la salida con él: podemos hacer que esté alineada a la derecha, a la izquierda o dejar un espacio dedicado para la información. Comencemos con un ejemplo simple. Para crear el archivo, usa tu editor de texto habitual (kate, nano, vi, ...) y crea un archivo llamado note.sh con estos comandos:

echo "Phone number ?"

¿Cómo ejecutar/correr un script?

Después de guardar el archivo, podemos ejecutarlo con el comando bash, pasando nuestro archivo como argumento:

[root@localhost ~]# bash note.sh
Phone number ?

En realidad, ejecutar el script de esta manera es muy inconveniente. Si no usamos elbashcomando como prefijo para ejecutarlo, será más cómodo. Para hacer que el script sea ejecutable, podemos usar elchmodcomando:

[root@localhost ~]# ls -la note.sh
-rw-r--r--. 1 root root 22 Apr 23 20:52 note.sh
[root@localhost ~]# chmod +x note.sh
[root@localhost ~]# ls -la note.sh
-rwxr-xr-x. 1 root root 22 Apr 23 20:52 note.sh
[root@localhost ~]#

Shell脚本编程初体验

Nota: el comando ls muestra los archivos de la carpeta actual. Al añadir la opción -la, muestra más información sobre los archivos.

Como podemos ver, antes de que se ejecutara elchmodcomando, el script solo tenía permisos de lectura (r) y escritura (w). Después de ejecutarchmod +xel comando, obtuvo el permiso de ejecución (x). (Daré más detalles sobre los permisos en el próximo artículo.) Ahora, solo tenemos que ejecutarlo así:

[root@localhost ~]# ./note.sh
Phone number ?

Antes del nombre del script, agregué la combinación ./ . El . (punto) en el mundo Unix significa ubicación actual (carpeta actual), / (barra) es el separador de carpetas. (En los sistemas Windows, usamos la barra invertida (\) para la misma función). Entonces, toda esta combinación significa: "ejecutar el script note.sh desde la carpeta actual". Creo que si ejecuto este script con la ruta completa, te resultará más claro:

[root@localhost ~]# /root/note.sh
Phone number ?
[root@localhost ~]#

También funciona.

Si todos los usuarios de Linux tuvieran el mismo shell predeterminado, todo estaría bien. Si simplemente ejecutamos el script, el shell de usuario predeterminado se usará para interpretar el contenido del script y ejecutar los comandos. Los distintos shells tienen pequeñas diferencias en sintaxis, comandos internos, etc. Por lo tanto, para asegurarnos de que nuestro script usarábash, deberíamos añadir#!/bin/basha la primera línea del archivo. Así, el shell de usuario predeterminado invocará/bin/bash, y solo entonces se ejecutarán los comandos del script:

[root@localhost ~]# cat note.sh
#!/bin/bash
echo "Phone number ?"

Hasta ahora, estamos 100% seguros de quebashse usará para interpretar el contenido de nuestro script. Continuemos.

Leer la entrada

Después de mostrar el mensaje, el script esperará la respuesta del usuario. Hay unreadcomando para recibir la respuesta del usuario:

#!/bin/bash
echo "Phone number ?"
read phone

Después de ejecutarse, el script esperará la entrada del usuario hasta que presione la tecla [ENTER] para finalizar la entrada:

[root@localhost ~]# ./note.sh
Phone number ?
12345                               <--- 这儿是我输入的内容
[root@localhost ~]#

Todo lo que ingreses se almacenará en la variablephonePara mostrar el valor de la variable, también podemos usar elechocomando:

[root@localhost ~]# cat note.sh
#!/bin/bash
echo "Phone number ?"
read phone
echo "You have entered $phone as a phone number"
[root@localhost ~]# ./note.sh
Phone number ?
123456
You have entered 123456 as a phone number
[root@localhost ~]#

EnbashEn el shell, generalmente usamos el$símbolo (dólar) para indicar que esto es una variable, excepto al leer en una variable y en otros pocos casos donde no se usa este $ (se explicará más adelante).

Bien, ahora estamos listos para agregar las preguntas restantes:

#!/bin/bash
echo "Phone number?"
read phone
echo "Name?"
read name
echo "Issue?"
read issue
[root@localhost ~]# ./note.sh
Phone number?
123
Name?
Jim
Issue?
script is not working.
[root@localhost ~]#

Usar la redirección de flujos

¡Perfecto! Lo que queda es redirigir todo al archivo data.txt. Como separador de campos, usaremos el símbolo / (barra).

Nota: Puedes elegir cualquier separador que consideres mejor, pero asegúrate de que el contenido del archivo no contenga esos símbolos, de lo contrario podría generar campos adicionales en la línea de texto.

No olvides usar ">>" en lugar de ">", ¡porque queremos añadir la salida al final del archivo!

[root@localhost ~]# tail -2 note.sh
read issue
echo "$phone/$name/$issue">>data.txt
[root@localhost ~]# ./note.sh
Phone number?
987
Name?
Jimmy
Issue?
Keybord issue.
[root@localhost ~]# cat data.txt
987/Jimmy/Keybord issue.
[root@localhost ~]#

Nota : tailEl comando muestra las últimasnlíneas del archivo.

¡Listo! Ejecutémoslo de nuevo para ver:

[root@localhost ~]# ./note.sh
Phone number?
556
Name?
Janine
Issue?
Mouse was broken.
[root@localhost ~]# cat data.txt
987/Jimmy/Keybord issue.
556/Janine/Mouse was broken.
[root@localhost ~]#

Nuestro archivo está creciendo. Agreguemos una fecha al principio de cada línea; esto será útil cuando más adelante manipulemos estas estadísticas. Para lograrlo, podemos usar el comando date y especificar un formato, porque no me gusta el formato predeterminado:

[root@localhost ~]# date
Thu Apr 23 21:33:14 EEST 2015                     <---- date命令的默认输出
[root@localhost ~]# date "+%Y.%m.%d %H:%M:%S"
2015.04.23 21:33:18                               <---- 格式化后的输出

Hay varias formas de leer la salida de un comando a una variable. En este caso simple, usaremos ` (es una comilla invertida, no una comilla simple; está en la misma tecla que la tilde ~):

[root@localhost ~]# cat note.sh
#!/bin/bash
now=`date "+%Y.%m.%d %H:%M:%S"`
echo "Phone number?"
read phone
echo "Name?"
read name
echo "Issue?"
read issue
echo "$now/$phone/$name/$issue">>data.txt
[root@localhost ~]# ./note.sh
Phone number?
123
Name?
Jim
Issue?
Script hanging.
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
[root@localhost ~]#

Mmm... nuestro script se ve un poco feo. Vamos a embellecerlo. Si quieres leer manualmentereadel comando, notarás que el comando read también puede mostrar información. Para lograr eso, deberíamos usar la opción -p junto con el mensaje:

[root@localhost ~]# cat note.sh
#!/bin/bash
now=`date "+%Y.%m.%d %H:%M:%S"`
read -p "Phone number: " phone
read -p "Name: " name
read -p "Issue: " issue
echo "$now/$phone/$name/$issue">>data.txt

Puedes encontrar mucha información interesante sobre varios comandos directamente desde la consola, simplemente escribiendo:man read, man echo, man date, man ……

¿De acuerdo? ¡Se ve mucho más cómodo!

[root@localhost ~]# ./note.sh
Phone number: 321
Name: Susane
Issue: Mouse was stolen
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
[root@localhost ~]#

El cursor está después del mensaje (no en una nueva línea), lo cual es interesante. (Nota del traductor de LCTT: si usas el comando echo para mostrar la salida, puedes usar el parámetro -n para evitar el salto de línea).

Bucles

Es hora de mejorar nuestro script. Si el usuario está todo el día atendiendo llamadas, ¿no sería molesto tener que ejecutarlo cada vez? Hagamos que estas actividades se repitan en un bucle sin fin:

[root@localhost ~]# cat note.sh
#!/bin/bash
while true
do
        read -p "Phone number: " phone
        now=`date "+%Y.%m.%d %H:%M:%S"`
        read -p "Name: " name
        read -p "Issue: " issue
        echo "$now/$phone/$name/$issue">>data.txt
done

He intercambiado lasread phoneynow=dateposiciones de las líneas. Esto es porque quiero obtener la hora después de ingresar el número de teléfono. Si lo pusiera en la primera línea del bucle, después de una iteración, la variable now obtendría la hora justo después de que los datos se almacenen en el archivo. Y eso no es bueno, porque la siguiente llamada podría ocurrir 20 minutos después, o incluso más tarde.

[root@localhost ~]# ./note.sh
Phone number: 123
Name: Jim
Issue: Script still not works.
Phone number: 777
Name: Daniel
Issue: I broke my monitor
Phone number: ^C
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
2015.04.23 21:47:55/123/Jim/Script still not works.
2015.04.23 21:48:16/777/Daniel/I broke my monitor
[root@localhost ~]#

Nota: Para salir del bucle infinito, puedes presionar [Ctrl]+[C]. El shell mostrará ^ para indicar la tecla CTRL.。

Usar la redirección con tuberías (pipes)

Agreguemos más funciones a nuestro "Frankenstein". Quiero que el script muestre algunas estadísticas después de cada llamada. Por ejemplo, quiero ver cuántas veces me ha llamado cada número. Para esto, deberíamos hacer cat del archivo data.txt:

[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
2015.04.23 21:47:55/123/Jim/Script still not works.
2015.04.23 21:48:16/777/Daniel/I broke my monitor
2015.04.23 22:02:14/123/Jimmy/New script also not working!!!
[root@localhost ~]#

Ahora, toda la salida la podemos redirigir alcutcomando, permitiendo quecutcorte cada línea en trozos (usamos el separador "/"), y luego imprima el segundo campo:

[root@localhost ~]# cat data.txt | cut -d"/" -f2
123
321
123
777
123
[root@localhost ~]#

Ahora, podemos redirigir esta salida a otro comandosort:

[root@localhost ~]# cat data.txt | cut -d"/" -f2|sort
123
123
123
321
777
[root@localhost ~]#

y dejar solo las líneas únicas. Para contar las entradas únicas, solo añade la-copción aluniqcomando:

[root@localhost ~]# cat data.txt | cut -d"/" -f2 | sort | uniq -c
    3 123
    1 321
    1 777
[root@localhost ~]#

Solo agrega esto al final de nuestro bucle:

#!/bin/bash
while true
do
        read -p "Phone number: " phone
        now=`date "+%Y.%m.%d %H:%M:%S"`
        read -p "Name: " name
        read -p "Issue: " issue
        echo "$now/$phone/$name/$issue">>data.txt
        echo "===== We got calls from ====="
        cat data.txt | cut -d"/" -f2 | sort | uniq -c
        echo "--------------------------------"
done

Ejecutar:

[root@localhost ~]# ./note.sh
Phone number: 454
Name: Malini
Issue: Windows license expired.
===== We got calls from =====
    3 123
    1 321
    1 454
    1 777
--------------------------------
Phone number: ^C

Shell脚本编程初体验

El escenario actual abarca varios pasos conocidos:

  • Mostrar mensaje
  • Obtener entrada del usuario
  • Almacenar el valor en un archivo
  • Procesar los datos almacenados

Pero, si el usuario es un poco responsable, a veces necesita ingresar datos, a veces necesita estadísticas, o quizás buscar algo en los datos almacenados. Para estas cosas, necesitamos usar switches/cases y saber cómo formatear bien la salida. Esto es útil para "dibujar" tablas en el shell.

Fuente: http://linux.cn/article-5591-1.html