diff --git a/README.md b/README.md index 336f67d..53d0871 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,9 @@ Founded in 2012 with offices in New York and Cambridge, MA, DigitalOcean offers For more information, please visit [https://www.digitalocean.com](https://www.digitalocean.com) or follow [@digitalocean](https://twitter.com/digitalocean) on Twitter. -If you are new to DigitalOcean, you can get a free $100 credit and spin up your own servers via this referral link here: +If you are new to DigitalOcean, you can get a free $200 credit and spin up your own servers via this referral link here: -[Free $100 Credit For DigitalOcean](https://m.do.co/c/2a9bba940f39) +[Free $200 Credit For DigitalOcean](https://m.do.co/c/2a9bba940f39) ### 👩‍💻 DevDojo @@ -112,18 +112,6 @@ If you prefer watching videos rather than reading, you can find a quick crash co **[Introduction to Bash Scripting Mini Video Crash Course](https://www.youtube.com/playlist?list=PLY7SzAmnEqp78tsHh1kK0gOx_f-uwQZAT)** -## 💻 Interactive training - -You can follow the interactive training online here: - -[Introduction to Bash Scripting Interactive training](https://ebook.bobby.sh/training.html) - -The training was built with Katacoda. You can find the Katacoda repository [here](https://github.com/bobbyiliev/katacoda). - -For more information on how to use Katacoda make sure to follow the steps from this tutorial here: - -[How to Use Katacoda to Create Highly Engaging Training Tutorials](https://devdojo.com/bobbyiliev/how-to-use-katacoda-to-create-highly-engaging-training-tutorials) - ## 👋 About the author My name is Bobby Iliev, and I have been working as a Linux DevOps Engineer since 2014. I am an avid Linux lover and supporter of the open-source movement philosophy. I am always doing that which I cannot do in order that I may learn how to do it, and I believe in sharing knowledge. @@ -156,7 +144,6 @@ If you ever need to create a graphic, poster, invitation, logo, presentation – - [Ibis](https://github.com/themsaid/ibis/) - [Canva](https://www.canva.com/) - [Tails](http://devdojo.com/tails) -- [Katacoda](https://www.katacoda.com/) ## 📖 Other eBooks diff --git a/ebook/en/content/009-bash-conditional-expressions.md b/ebook/en/content/009-bash-conditional-expressions.md index 5a96980..339f50d 100644 --- a/ebook/en/content/009-bash-conditional-expressions.md +++ b/ebook/en/content/009-bash-conditional-expressions.md @@ -85,9 +85,11 @@ Here is a list of the most popular Bash conditional expressions. You do not have * True if the shell variable varname is set (has been assigned a value). ```bash -[[ -v ${varname} ]] +[[ -v varname ]] ``` +> Here, `varname` is the name of the variable. The `-v` operator expects a variable name as an argument rather than a value, so if you pass `${varname}` instead of `varname`, the expression will return false. + True if the length of the string is zero. ```bash diff --git a/ebook/en/content/016-creating-an-interactive-menu-in-bash.md b/ebook/en/content/016-creating-an-interactive-menu-in-bash.md index 6666eb5..2695b14 100644 --- a/ebook/en/content/016-creating-an-interactive-menu-in-bash.md +++ b/ebook/en/content/016-creating-an-interactive-menu-in-bash.md @@ -83,6 +83,7 @@ At the beginning of your script add the following color functions: ## green='\e[32m' blue='\e[34m' +red='\e[31m' clear='\e[0m' ## @@ -127,8 +128,8 @@ $(ColorBlue 'Choose an option:') " 3) tcp_check ; menu ;; 4) kernel_check ; menu ;; 5) all_checks ; menu ;; - 0) exit 0 ;; - *) echo -e $red"Wrong option."$clear; WrongCommand;; + 0) exit 0 ;; + *) echo -e "${red}Wrong option.${clear}"; menu ;; esac } ``` @@ -165,7 +166,7 @@ Finally, we have a switch case which triggers a different function depending on 4) kernel_check ; menu ;; 5) all_checks ; menu ;; 0) exit 0 ;; - *) echo -e $red"Wrong option."$clear; WrongCommand;; + *) echo -e "${red}Wrong option.${clear}"; menu ;; esac ``` @@ -236,6 +237,7 @@ function all_checks() { ## green='\e[32m' blue='\e[34m' +red='\e[31m' clear='\e[0m' ## @@ -267,7 +269,7 @@ $(ColorBlue 'Choose an option:') " 4) kernel_check ; menu ;; 5) all_checks ; menu ;; 0) exit 0 ;; - *) echo -e $red"Wrong option."$clear; WrongCommand;; + *) echo -e "${red}Wrong option.${clear}"; menu ;; esac } diff --git a/ebook/en/content/023-bash-redirection.md b/ebook/en/content/023-bash-redirection.md index 7339e26..fc4a94e 100644 --- a/ebook/en/content/023-bash-redirection.md +++ b/ebook/en/content/023-bash-redirection.md @@ -13,9 +13,9 @@ In Linux, there are 3 File Descriptors, **STDIN** (0); **STDOUT** (1) and **STDE # Difference between Pipes and Redirections -Both *pipes* and *redidertions* redirect streams `(file descriptor)` of process being executed. The main difference is that *redirections* deal with `files stream`, sending the output stream to a file or sending the content of a given file to the input stream of the process. +Both *pipes* and *redirections* redirect streams `(file descriptor)` of process being executed. The main difference is that *redirections* deal with `files stream`, sending the output stream to a file or sending the content of a given file to the input stream of the process. -On the other hand a pipe connects two commands by sending the output stream of the first one to the input stream of the second one. without any redidertions specified. +On the other hand a pipe connects two commands by sending the output stream of the first one to the input stream of the second one. without any redirections specified. # Redirection in Bash diff --git a/ebook/en/export/introduction-to-bash-scripting-dark.pdf b/ebook/en/export/introduction-to-bash-scripting-dark.pdf index e014fcf..a91ff17 100644 Binary files a/ebook/en/export/introduction-to-bash-scripting-dark.pdf and b/ebook/en/export/introduction-to-bash-scripting-dark.pdf differ diff --git a/ebook/en/export/introduction-to-bash-scripting-light.pdf b/ebook/en/export/introduction-to-bash-scripting-light.pdf index a6a002f..c7a580a 100644 Binary files a/ebook/en/export/introduction-to-bash-scripting-light.pdf and b/ebook/en/export/introduction-to-bash-scripting-light.pdf differ diff --git a/ebook/en/export/introduction-to-bash-scripting.epub b/ebook/en/export/introduction-to-bash-scripting.epub index 2147c1d..c5077ca 100644 Binary files a/ebook/en/export/introduction-to-bash-scripting.epub and b/ebook/en/export/introduction-to-bash-scripting.epub differ diff --git a/ebook/en/export/sample-.introduction-to-bash-scripting-dark.pdf b/ebook/en/export/sample-.introduction-to-bash-scripting-dark.pdf index cdb18ee..3a2b03e 100644 Binary files a/ebook/en/export/sample-.introduction-to-bash-scripting-dark.pdf and b/ebook/en/export/sample-.introduction-to-bash-scripting-dark.pdf differ diff --git a/ebook/en/export/sample-.introduction-to-bash-scripting-light.pdf b/ebook/en/export/sample-.introduction-to-bash-scripting-light.pdf index 3fa2164..71d6d14 100644 Binary files a/ebook/en/export/sample-.introduction-to-bash-scripting-light.pdf and b/ebook/en/export/sample-.introduction-to-bash-scripting-light.pdf differ diff --git a/ebook/es/content/022-bash-password-generator.md b/ebook/es/content/022-bash-password-generator.md new file mode 100644 index 0000000..4f4cacd --- /dev/null +++ b/ebook/es/content/022-bash-password-generator.md @@ -0,0 +1,126 @@ +# Script de Bash: Generador de contraseñas + +Es común la situación en la que necesitará generar una contraseña aleatoria que pueda usar para cualquier instalación de software, o cuando se registre en cualquier sitio web. + +Hay muchas opciones con el fin de lograr esto. Puede utilizar un gestor de contraseñas, donde a menudo tiene la opción de generar una contraseña al azar, o utilizar un sitio web que puede generar la contraseña. + +También puede utilizar Bash en su terminal (línea de comandos) para generar una contraseña que pueda utilizar rápidamente. Hay muchas maneras de lograrlo y me aseguraré de cubrir algunas de ellas y dejaré que elija la opción que más se adapte a sus necesidades. + +## :warning: Seguridad + +**Este script está pensado para practicar sus habilidades con BASH. Puede divertirse haciendo proyectos simples con BASH, pero la seguridad no es una broma, así que por favor asegúrese de no guardar sus contraseñas en texto plano en un archivo local, ni escribirlas a mano en un pedazo de papel.** + +**Recomiendo encarecidamente a todo el mundo que utilice proveedores seguros y de confianza para generar y guardar las contraseñas. + +## Resumen del script + +Permítame primero hacer un rápido resumen de lo que nuestro script va a hacer: + +1. Tendremos la opción de elegir la longitud de los caracteres de la contraseña cuando se ejecute el script. +2. El script generará entonces 5 contraseñas aleatorias con la longitud que se especificó en el paso 1 + +## Requisitos previos + +Necesitará un terminal bash y un editor de texto. Puede usar cualquier editor de texto como vi, vim, nano o Visual Studio Code. + +Estoy ejecutando el script localmente en mi portátil GNU/Linux pero si está usando un PC Windows puede hacer ssh a cualquier servidor de su elección y ejecutar el script allí. + +Aunque el script es bastante simple, tener algunos conocimientos básicos de BASH le ayudará a entender mejor el script y cómo funciona. + +## Generar una contraseña aleatoria +Uno de los grandes beneficios de GNU/Linux es que puede hacer un montón de cosas usando diferentes métodos. Cuando se trata de generar una cadena aleatoria de caracteres tampoco es diferente. + +Puede usar varios comandos para generar una cadena de caracteres aleatorios. Cubriré algunos de ellos y proporcionaré algunos ejemplos. + +- Usando el comando ```date```. +El comando ```date``` mostrará la fecha y hora actuales. Sin embargo, también podemos manipular la salida con el fin de utilizarla como contraseña generada aleatoriamente. Podemos generar un hash a partir de la fecha utilizando md5, sha o simplemente codificarlo usando base64. Estos son algunos ejemplos: + +``` +date | md5sum +94cb1cdecfed0699e2d98acd9a7b8f6d - +``` +Usando sha256sum: + +``` +date | sha256sum +30a0c6091e194c8c7785f0d7bb6e1eac9b76c0528f02213d1b6a5fbcc76ceff4 - +``` +Usando base64: +``` +date | base64 +0YHQsSDRj9C90YMgMzAgMTk6NTE6NDggRUVUIDIwMjEK +``` + +- También podemos usar openssl para generar bytes pseudoaleatorios y pasar la salida por base64. Un ejemplo de salida será: +``` +openssl rand -base64 10 +9+soM9bt8mhdcw== +``` +Tenga en cuenta que openssl podría no estar instalado en su sistema, por lo que es probable que necesite instalarlo primero para poder utilizarlo. + +- La forma preferida es utilizar el generador de números pseudoaleatorios - /dev/urandom ya que está pensado para la mayoría de los propósitos criptográficos. También necesitaríamos manipular la salida usando ```tr`` para traducirla. Un ejemplo de comando es: + +``` +tr -cd '[:alnum:]' < /dev/urandom | fold -w10 | head -n 1 +``` +Con este comando tomamos la salida de /dev/urandom y la traducimos con ```tr`` utilizando todas las letras y dígitos e imprimimos el número de caracteres deseado. + +## El script +Primero comenzamos el script con el shebang. Lo usamos para decirle al sistema operativo qué intérprete usar para analizar el resto del archivo. +``` +#!/bin/bash +``` +Entonces podemos continuar y pedir al usuario alguna entrada. En este caso nos gustaría saber cuántos caracteres debe tener la contraseña: + +``` +# Pregunta al usuario la longitud de la contraseña +clear +printf "\n" +read -p "¿Cuántos caracteres quiere que tenga la contraseña? " longitud_contrasena +printf "\n" +``` +Generar las contraseñas y luego imprimirla para que el usuario pueda usarla. +``` +# ¡Aquí es donde ocurre la magia! +# Generar una lista de 10 cadenas y cortarla al valor deseado proporcionado por el usuario. + +for i in {1..10}; do (tr -cd '[:alnum:]' < /dev/urandom | fold -w${longitud_contrasena} | head -n 1); done + +# Imprimir las cadenas +printf "$pass_output\n" +printf "Adiós, ${USER}\n" +``` + +## El script completo: +``` +#!/bin/bash +#============================================= +# Generador de contraseñas con opción de login +#============================================= + +# Pregunta al usuario por la longitud de la cadena +clear +printf "\n" +read -p "¿Cuántos caracteres quiere que tenga la contraseña? " longitud_contrasena +printf "\n" + +# ¡Aquí es donde ocurre la magia! +# Generar una lista de 10 cadenas y cortarla al valor deseado proporcionado por el usuario. + +for i in {1..10}; do (tr -cd '[:alnum:]' < /dev/urandom | fold -w${longitud_contrasena} | head -n 1); done + +# Imprimir las cadenas +printf "$pass_output\n" +printf "Adiós, ${USER}\n" +``` + +## Conclusión +Esto es más o menos cómo se puede utilizar un simple script de bash para generar contraseñas aleatorias. + +:advertencia: **Como ya se ha mencionado, por favor asegúrese de utilizar contraseñas seguras con el fin de asegurarse de que su cuenta está protegida. También, siempre que sea posible, utilice la autenticación de 2 factores, ya que esto proporcionará una capa adicional de seguridad para su cuenta. + +Aunque el script funciona correctamente, espera que el usuario introduzca los datos solicitados correctamente. Con el fin de evitar cualquier problema, tendría que hacer algunos controles más posteriores a la entrada del usuario, con el fin de asegurarse de que el script seguirá funcionando bien, incluso si la entrada proporcionada no coincide con nuestras necesidades. + +## Contribución de +[Alex Georgiev](https://twitter.com/alexgeorgiev17) + diff --git a/ebook/es/content/023-bash-redirection.md b/ebook/es/content/023-bash-redirection.md new file mode 100644 index 0000000..a57ea55 --- /dev/null +++ b/ebook/es/content/023-bash-redirection.md @@ -0,0 +1,224 @@ +# Redirección en Bash + +Un superusuario de Linux debe tener un buen conocimiento de las pipes y la redirección en Bash. Es un componente esencial del sistema y a menudo es útil en el campo de la Administración de Sistemas Linux. + +Cuando ejecuta un comando como ``ls``, ``cat``, etc., obtiene una salida en la terminal. Si escribe un comando incorrecto, pasa una bandera incorrecta o un argumento incorrecto en la línea de comandos, obtendrá un error en la terminal. +En ambos casos, se le da algo de texto. Puede parecerle "sólo texto", pero el sistema trata este texto de forma diferente. Este identificador se conoce como Descriptor de Archivo (fd). + +En Linux, hay 3 Descriptores de Archivos, **STDIN** (0); **STDOUT** (1) y **STDERR** (2). + +**STDIN** (fd: 0): Gestiona la entrada en el terminal. +**STDOUT** (fd: 1): Gestiona el texto de salida en el terminal. +**STDERR** (fd: 2): Gestiona el texto de error en el terminal. + +# Diferencia entre Pipes y Redirecciones + +Tanto *pipes* como *redirections* redirigen flujos `(descriptor de archivo)` del proceso que se está ejecutando. La principal diferencia es que las *redirecciones* tratan con `flujos de archivos`, enviando el flujo de salida a un archivo o enviando el contenido de un archivo dado al flujo de entrada del proceso. + +Por otro lado una pipe conecta dos comandos enviando el flujo de salida del primero al flujo de entrada del segundo, sin ninguna redirección especificada. + +# Redirección en Bash + +## STDIN (Entrada Estándar) +Cuando introduce algún texto de entrada para un comando que lo solicita, en realidad está introduciendo el texto en el descriptor de archivo **STDIN**. Ejecute el comando ``cat`` sin ningún argumento en la línea de comandos. +Puede parecer que el proceso se ha detenido, pero en realidad es ``cat`` preguntando por **STDIN**. ``cat`` es un programa sencillo e imprimirá el texto pasado a **STDIN**. Sin embargo, puede extender el caso de uso redirigiendo la entrada a los comandos que toman **STDIN**. + +Ejemplo con ``cat``: +``` +cat << EOF +¡Hola Mundo! +¿Cómo estás? +EOF +``` +Esto simplemente imprimirá el texto proporcionado en la terminal: +``` +¡Hola Mundo! +¿Cómo estás? +``` +Lo mismo se puede hacer con otros comandos que toman la entrada a través de STDIN. Como, ``wc``: +``` +wc -l << EOF +¡Hola Mundo! +¿Cómo estás? +EOF +``` +La bandera ``-l`` usada con ``wc`` cuenta el número de líneas. +Este bloque de código bash imprimirá el número de líneas en la terminal: +``` +2 +``` + +## STDOUT (Salida estándar) +El texto normal (que no sea error) ue aparece en la pantalla de su terminal se imprime a través del descriptor de archivo **STDOUT**. El **STDOUT** de un comando puede ser redirigido a un archivo, de tal manera que la salida del comando se escriba en un archivo en lugar de imprimirse en la pantalla de la terminal. +Esto se hace simplemente con la ayuda de los operadores ``>`` y ``>>``. + +Ejemplo: +``` +echo "¡Hola Mundo!" > archivo.txt +``` +El comando anterior no imprimirá "¡Hola Mundo!" en la terminal, en su lugar creará un archivo llamado ``archivo.txt`` y escribirá la cadena de texto "¡Hola Mundo!" en el mismo. +Esto se puede comprobar ejecutando el comando ``cat`` en el archivo ``archivo.txt``. +``` +cat archivo.txt +``` +Sin embargo, cada vez que redirija el **STDOUT** de cualquier comando varias veces al mismo archivo, eliminará el contenido existente del archivo para escribir el nuevo. + +Ejemplo: +``` +echo "¡Hola Mundo!" > archivo.txt +echo "¿Cómo estás?" > archivo.txt +``` + +Al ejecutar ``cat`` en el archivo ``archivo.txt``: +``` +cat archivo.txt +``` + +Sólo se imprimirá la cadena de texto "¿Cómo estás?". +``` +¿Cómo estás? +``` + +Esto se debe a que la cadena de text "¡Hola Mundo!" ha sido sobrescrita. +Este comportamiento puede evitarse utilizando el operador ``>>``. + +El ejemplo anterior se puede escribir como: +``` +echo "¡Hola Mundo!" > archivo.txt +echo "¿Cómo estás?" >> archivo.txt +``` + +Al ejecutar ``cat`` en el archivo ``archivo.txt``, obtendrá el resultado deseado. +``` +¡Hola Mundo! +¿Cómo estás? +``` + +Alternativamente, el operador de redirección para **STDOUT** también puede escribirse como ``1>``. Así, +``` +echo "¡Hola Mundo!" 1> file.txt +``` + +## STDERR (Error estándar) +El texto de error en la terminal se imprime a través de **STDERR**. Por ejemplo: +``` +ls --hola +``` +daría un mensaje de error. Este mensaje de error es la salida del flujo **STDERR** del comando. + +**STDERR** puede ser redirigido usando el operador ``2>``. +``` +ls --hello 2> error.txt +``` + +Este comando redirigirá el mensaje de error al archivo ``error.txt`` y lo escribirá en él. Esto puede verificarse ejecutando el comando ``cat`` en el archivo ``error.txt``. + +También puede usar el operador ``2>>`` para **STDERR** del mismo que usó ``>>`` para **STDOUT**. + +Los mensajes de error en scripts Bash pueden ser indeseables a veces. Puede elegir ignorarlos redirigiendo el mensaje de error al archivo ``/dev/null``. +``/dev/null`` es un pseudo-dispositivo que recibe texto y lo descarta inmediatamente. + +El ejemplo anterior puede escribirse de la siguiente manera para ignorar completamente el texto de error: +``` +ls --hello 2> /dev/null +``` + +Por supuesto, puede redirigir tanto **STDOUT** como **STDERR** en el mismo comando o script. +``` +./instalar_paquete.sh > salida.txt 2> error.txt +``` + +Ambos pueden ser redirigidos al mismo archivo también. +``` +./instalar_paquete.sh > archivo.txt 2> archivo.txt +``` + +También hay una forma más corta de hacer esto. +``` +./instalar_paquete.sh > archivo.txt 2>&1 +``` + +# Canalización (Piping) + +Hasta ahora hemos visto como redirigir el **STDOUT**, **STDIN** y **STDOUT** desde y hacia un archivo. +Para concatenar la salida del programa *(comando)* como entrada de otro programa *(comando) * se puede utilizar una barra vertical `|`. Esto se conoce como canalización (más popularmente por su nombre en inglés: ``piping``). + +Ejemplo: +``` +ls | grep ".txt" +``` +Este comando listará los archivos en el directorio actual y pasará la salida al comando *`grep`* que entonces filtrará la salida para mostrar sólo los archivos que contengan la cadena ".txt". + +Sintaxis: +``` +[time [-p]] [!] [comando1 [ | ó |& comando2 ] ... +``` + +También puede construir cadenas arbitrarias de comandos enlazándolos para conseguir un resultado potente. + +Este ejemplo crea un listado de todos los usuarios que poseen un archivo en un directorio dado, así como cuántos archivos y directorios poseen: +``` +ls -l /proyectos/bash_scripts | tail -n +2 | sed 's/\s\s*/ /g' | cut -d ' ' -f 3 | sort | uniq -c +``` + +Salida: +``` +8 anne +34 harry +37 tina +18 ryan +``` + +# HereDocument + +El símbolo `<<` se puede utilizar para crear un archivo temporal [heredoc] y redirigir desde él en la línea de comandos. +``` +COMANDO << EOF + ContenidoDelDocumento + ... + ... +EOF +``` +Observe que `EOF` representa el delimitador (fin de archivo) del heredoc. De hecho, podemos utilizar cualquier palabra alfanumérica en su lugar para indicar el inicio y el final del archivo. Por ejemplo, ésta es un heredoc válido: +``` +cat << palabraaleatoria1 + Este script imprimirá estas líneas en la terminal. + Tenga en cuenta que cat puede leer desde la entrada estándar. Usando este heredoc, podemos + crear un archivo temporal con estas líneas como su contenido y canalizar ello + hacia cat. +palabraaleatoria1 +``` + +Efectivamente, parecerá como si el contenido del heredoc se introdujera en el comando. Esto puede hacer el script muy limpio si múltiples líneas necesitan ser canalizadas dentro de un programa. + +Además, podemos adjuntar más pipes como se muestra: +``` +cat << palabraaleatoria1 | wc + Este script imprimirá estas líneas en la terminal. + Tenga en cuenta que cat puede leer desde la entrada estándar. Usando este heredoc, podemos + crear un archivo temporal con estas líneas como su contenido y canalizar ello + hacia cat. +palabraaleatoria1 +``` +También se pueden usar variables predefinidas dentro de los heredocs. + +# Herestrings + +Herestrings son bastante similares a heredocs pero usan `<<<`. Estos se utilizan para cadenas de una sola línea que tienen que ser canalizados en algún programa. Esto parece más limpio que heredocs ya que no tenemos que especificar el delimitador. +``` +wc <<<"esta es una forma fácil de pasar cadenas a la entrada estándar de un programa (en este caso wc)» +``` + +Al igual que los heredocs, los herestrings pueden contener variables. + +## Resumen +|**Operador** |**Descripción** | +|:---|:---| +|`>`|`Guardar la salida a un archivo`| +|`>>`|`Añadir la salida a un archivo`| +|`<`|`Leer una entrada desde un archivo`| +|`2>`|`Redirigir mensajes de error`| +|`\|`|`Enviar la salida de un programa como entrada a otro`| +|`<<`|`Canalizar múltiple líneas a un programa limpiamente`| +|`<<<`|`Canalizar una única línea a un programa limpiamente`| + diff --git a/index.html b/index.html index 5045fbd..9ba9ef1 100644 --- a/index.html +++ b/index.html @@ -38,8 +38,6 @@ class="mr-0 font-bold duration-100 md:mr-3 lg:mr-8 transition-color hover:text-indigo-600">Sponsors Chapters - Interactive Training Blog - -
-
-

- Introduction to Bash with Interactive training -

-

- Powered by - Katacoda

- -
- -
-
-
-
@@ -453,11 +425,6 @@

Enjoying the free eBook?

-
- - Interactive Training - -
Blog diff --git a/training.html b/training.html deleted file mode 100644 index 6d99a0a..0000000 --- a/training.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - Bobby Iliev - Introduction to Bash Scripting - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - -
-
-
- Free Download - -
-

Download the eBook for free!

-

Are you ready to learn Bash and start writing awesome Bash scripts?

-
- Free Download - -
-
-
- - - - - - \ No newline at end of file