Bash 102

Author

Jeronimo Miranda

Algunos trucos extra en BASH

Usar la terminal es un cambio respecto a lo que hacemos diariamente en la computadora. Significa usar muy poco el raton y escribir directamente todo lo que queremos que haga la computadora.

Sin embargo, esto de escribir todo puede llegar a ser cansado y tedioso. Existen atajos para escribir menos?

Expansion con “{}”

Uno de los atajos que usamos para escribir menos es el uso de la expansion con corchetes {}. La expresion dentro de los corchetes se “expande” para cada elemento.

echo pedro-{Pedro,PEDRO,pe}
pedro-Pedro pedro-PEDRO pedro-pe

Esto es util si no se quiere escribir un comando repetitivo, por ejemplo, digamos que queremos hacer un directorio para nuestro proyecto de metagenoma, pero queremos mantener organizado nuestro directorio con un sub-directorio para graficas, otro para los datos, y otro donde guardar nuestros scripts. Entonces, asumiendo que estamos en /home/nombredeusuario podemos escribir, en una linea: Es importante que no haya espacios dentro del {}, solo ,

mkdir -p metagenoma/{secuencias,scripts,analisis/graficas}
ls metagenoma
analisis
scripts
secuencias

Cuando hay mas de un grupo de corchetes, se expanden todas las combinaciones. Esto puede ser util para operaciones repetitivas en archivos. Por ejemplo, es comun trabajar con archivos de la forma nombre-Control1_Read1.fastq nombre-Control1_Read2.fastq. Como podemos procesar estos archivos sin tener que escribirlos todos o copiar y pegar muchas veces? Usamos echo que solo imprime los nombres, pero podriamos sustituir cualquier programa como fastq que usaremos esta semana:

echo nombre-{Control,Tratado}{1..9}_Read{1,2}.fastq
nombre-Control1_Read1.fastq nombre-Control1_Read2.fastq nombre-Control2_Read1.fastq nombre-Control2_Read2.fastq nombre-Control3_Read1.fastq nombre-Control3_Read2.fastq nombre-Control4_Read1.fastq nombre-Control4_Read2.fastq nombre-Control5_Read1.fastq nombre-Control5_Read2.fastq nombre-Control6_Read1.fastq nombre-Control6_Read2.fastq nombre-Control7_Read1.fastq nombre-Control7_Read2.fastq nombre-Control8_Read1.fastq nombre-Control8_Read2.fastq nombre-Control9_Read1.fastq nombre-Control9_Read2.fastq nombre-Tratado1_Read1.fastq nombre-Tratado1_Read2.fastq nombre-Tratado2_Read1.fastq nombre-Tratado2_Read2.fastq nombre-Tratado3_Read1.fastq nombre-Tratado3_Read2.fastq nombre-Tratado4_Read1.fastq nombre-Tratado4_Read2.fastq nombre-Tratado5_Read1.fastq nombre-Tratado5_Read2.fastq nombre-Tratado6_Read1.fastq nombre-Tratado6_Read2.fastq nombre-Tratado7_Read1.fastq nombre-Tratado7_Read2.fastq nombre-Tratado8_Read1.fastq nombre-Tratado8_Read2.fastq nombre-Tratado9_Read1.fastq nombre-Tratado9_Read2.fastq
touch metagenoma/secuencias/nombre-{Control,Tratado}{1..9}_Read{1,2}.fastq
ls metagenoma/secuencias
nombre-Control1_Read1.fastq
nombre-Control1_Read2.fastq
nombre-Control2_Read1.fastq
nombre-Control2_Read2.fastq
nombre-Control3_Read1.fastq
nombre-Control3_Read2.fastq
nombre-Control4_Read1.fastq
nombre-Control4_Read2.fastq
nombre-Control5_Read1.fastq
nombre-Control5_Read2.fastq
nombre-Control6_Read1.fastq
nombre-Control6_Read2.fastq
nombre-Control7_Read1.fastq
nombre-Control7_Read2.fastq
nombre-Control8_Read1.fastq
nombre-Control8_Read2.fastq
nombre-Control9_Read1.fastq
nombre-Control9_Read2.fastq
nombre-Tratado1_Read1.fastq
nombre-Tratado1_Read2.fastq
nombre-Tratado2_Read1.fastq
nombre-Tratado2_Read2.fastq
nombre-Tratado3_Read1.fastq
nombre-Tratado3_Read2.fastq
nombre-Tratado4_Read1.fastq
nombre-Tratado4_Read2.fastq
nombre-Tratado5_Read1.fastq
nombre-Tratado5_Read2.fastq
nombre-Tratado6_Read1.fastq
nombre-Tratado6_Read2.fastq
nombre-Tratado7_Read1.fastq
nombre-Tratado7_Read2.fastq
nombre-Tratado8_Read1.fastq
nombre-Tratado8_Read2.fastq
nombre-Tratado9_Read1.fastq
nombre-Tratado9_Read2.fastq

Piping: la filosofia modular de UNIX

Hasta ahora, hemos visto que los resultados de los comandos aparecen en la pantalla, a esto se le conoce como standard output.

echo "TGAATGAAAAATTTAAGCATTGTTTGCTTATTGTTCCAAGACATTGTCAATAAAAGCATTTAAGTTGAAT"
TGAATGAAAAATTTAAGCATTGTTTGCTTATTGTTCCAAGACATTGTCAATAAAAGCATTTAAGTTGAAT

En su lugar, podemos redirigir este output al standard input de otro programa, para esto, uno de los simbolos mas importantes en UNIX es la barra vertical | a la que tambien se le conoce como pipe o tuberia, porque transmite los datos de un lugar a otro. Por ejemplo aqui redirigimos al programa tr T U que cambia todas las Ts por Us para transformar la secuencia de DNA a RNA.

echo "TGAATGAAAAATTTAAGCATTGTTTGCTTATTGTTCCAAGACATTGTCAATAAAAGCATTTAAGTTGAAT" | tr T U 
UGAAUGAAAAAUUUAAGCAUUGUUUGCUUAUUGUUCCAAGACAUUGUCAAUAAAAGCAUUUAAGUUGAAU

Esto se sigue redireccionando al standar output despues de pasar por tr. Tambien podemos redireccionar a un archivo (y crear el archivo al mismo tiempo) con >

echo "TGAATGAAAAATTTAAGCATTGTTTGCTTATTGTTCCAAGACATTGTCAATAAAAGCATTTAAGTTGAAT" | tr T U > secuencia_rna.fa

En este caso no se imprimio nada a la pantalla, pero se creo el archivo secuencia_rna.fa

cat secuencia_rna.fa
UGAAUGAAAAAUUUAAGCAUUGUUUGCUUAUUGUUCCAAGACAUUGUCAAUAAAAGCAUUUAAGUUGAAU

Pero se nos olvido poner el nombre de la secuencia en el archivo! Ademas, queremos que todas las As sean minusculas y queremos el reverso de la secuencia, porque si. Con el menor que, < redirigimos al standard input.

tr A a < secuencia_rna.fa | rev | cat <(echo ">secuencia rna") -
>secuencia rna
UaaGUUGaaUUUaCGaaaaUaaCUGUUaCaGaaCCUUGUUaUUCGUUUGUUaCGaaUUUaaaaaGUaaGU

Esto parece complicado pero con un poco de practica, se vuelve simple y ademas muy comodo:

  1. < secuencia_rna.fa manda el contenido del archivo al standar input, que es recogido por

  2. tr A a que convierte todas las As mayusculas a minusculas, entonces el pipe | redirige el standard output de esto a

  3. rev que es un programa que invierte su standard input y lo escribe a standard output, con | lo volvemos a redirigir a

  4. cat un programa que concatena todos sus argumentos en orden. El primer argumento para cat es otro standard input que hicimos con <(echo ">secuencia rna") ademas del standard input que venia desde antes, que representamos con -. Que sucede si inviertes el orden de los dos argumentos de cat?

tr A a < secuencia_rna.fa | rev | cat - <(echo ">secuencia rna")
UaaGUUGaaUUUaCGaaaaUaaCUGUUaCaGaaCCUUGUUaUUCGUUUGUUaCGaaUUUaaaaaGUaaGU
>secuencia rna

Otro comando util aqui es >> que tambien redirige a un archivo al igual que > pero no sobreescribe el contenido, sino que solo lo anade al final.

tr A a < secuencia_rna.fa | rev | cat <(echo ">secuencia rna") - >> secuencia_rna.fa

cat secuencia_rna.fa
UGAAUGAAAAAUUUAAGCAUUGUUUGCUUAUUGUUCCAAGACAUUGUCAAUAAAAGCAUUUAAGUUGAAU
>secuencia rna
UaaGUUGaaUUUaCGaaaaUaaCUGUUaCaGaaCCUUGUUaUUCGUUUGUUaCGaaUUUaaaaaGUaaGU

Ten cuidado! Por que en este caso estamos leyendo y escribiendo al mismo archivo. Que ocurre cuando corres exactamente el mismo comando dos o tres veces? Como cambia el archivo secuencia_rna.fa?

Algunos programas tienen otro output que no es para resultados sino para los errores conocido como standard error. Aunque no lo veremos aqui, si trabajas mucho con comandos como los que hemos visto, quizas quieras investigar sobre 2> y el comando tee y si te sientes con mas confianza, xargs.

Como todo, hay un balance entre la simplicidad y la curva de aprendizaje, no tienes que aprenderte todos estos comandos, pero conforme te veas en la necesidad de trabajar mas y mas en la terminal, es util saber que existe una manera facil de procesar cientos o miles de archivos… Sientete libre de experimentar.

Scripting en bash

Una vez que tengamos practica con los comandos arriba mencionados, nuestros programas comenzaran a volverse mas complicados. Ya no es practico escribir un comando en la terminal, esperar a que termine, correr el siguiente comando, etc. etc. En su lugar, podemos escribir todos los comandos en un archivo y correr todo de una vez, esto se conoce como bash scripting.

Llamamos a vi bashscript.sh y escribimos:

#!/bin/bash


#Esta variable rara tiene el numero de argumentos
if [ "$#" -lt 1 ]
then
  echo "Quien eres?"
  exit 1
fi

echo "Hola $1, yo soy el programa $0 en $(pwd)"

Guardamos y salimos con ESC y :wq Ahora corremos el programa con:

./bashscript.sh $(whoami)
Hola jerolon, yo soy el programa ./bashscript.sh
1

No tenemos permiso? Veamos

ls -l | grep bashscript
-rwxrwxrwx 1 jerolon jerolon   407 Jun  9 18:41 bashscript.sh

La columna llena de rwx nos dice los permisos de la aplicacion que son: r permiso de lectura, w permiso de escritura, y x permiso de ejecucion. Esto esta repetido tres veces para el creador del archivo, el grupo del creador y todos los usuarios. Por ejemplo

-rw-rw-rw- Quiere decir que todos pueden leer y escribir pero no puede ser ejecutado por nadie.

-rwxr-xr-- Quiere decir que el creador puede leer y modificar y ejecutar, pero el grupo solo puede leer y ejecutar. Los que no pertenezcan al grupo, solo pueden leer.

chmod +x bashscript.sh
./bashscript.sh "Jeronimo"
Hola Jeronimo, yo soy el programa ./bashscript.sh
1

Ahora tenemos permiso para ejectuar. Incluso puedes ver que ls muestra el nombre del archivo en verde.

La condicion if es parecida a cualquier otro programa como python o R. Algunas comparaciones utiles al escribir scripts son:

#3 igual a 2?
test 2 -eq 3; echo $?;
test "ATG" = "TGA"; echo $?;
#4 mayor que 1?
test 4 -gt 1; echo "$?"
#es un directorio?
test -d metagenoma; echo "$?"
#es un archivo?
test -f metagenoma ; echo "$?"
#Existe?
test -e metagenoma/analisis; echo "$?"
echo "No sensible a mayusculas"
test -e metagenoma/Analisis; echo "$?"
#Es ejecutable?
test -x bashscript.sh; echo "$?"

Prueba a escribirlos en la terminal o incluirlos en bashscript.sh. 0 es cuando la condicion es verdadera, 1 cuando la condicion no se cumple. Todas estas condiciones pueden usarse dentro de un if [ ] para controlar el flujo del programa.

find

El comando find es una especie de ls pero con un poco mas de poder, nos permite buscar en directorios y subdirectorios con bastante control sobre los elementos que queremos encontrar. Con -type f decimos que solo nos de nombres de archivos, y con -name podemos darle un patron que debe incluir el nombre del archivo.

find metagenoma -type f -name "*Control5*" -or -name "*Tratado6*"
metagenoma/secuencias/nombre-Control5_Read1.fastq
metagenoma/secuencias/nombre-Control5_Read2.fastq
metagenoma/secuencias/nombre-Tratado6_Read1.fastq
metagenoma/secuencias/nombre-Tratado6_Read2.fastq

Aqui, listamos todos los archivos con el primer read y los ordenamos.

find metagenoma -type f -name "*_Read1.fastq*" | sort
metagenoma/secuencias/nombre-Control1_Read1.fastq
metagenoma/secuencias/nombre-Control2_Read1.fastq
metagenoma/secuencias/nombre-Control3_Read1.fastq
metagenoma/secuencias/nombre-Control4_Read1.fastq
metagenoma/secuencias/nombre-Control5_Read1.fastq
metagenoma/secuencias/nombre-Control6_Read1.fastq
metagenoma/secuencias/nombre-Control7_Read1.fastq
metagenoma/secuencias/nombre-Control8_Read1.fastq
metagenoma/secuencias/nombre-Control9_Read1.fastq
metagenoma/secuencias/nombre-Tratado1_Read1.fastq
metagenoma/secuencias/nombre-Tratado2_Read1.fastq
metagenoma/secuencias/nombre-Tratado3_Read1.fastq
metagenoma/secuencias/nombre-Tratado4_Read1.fastq
metagenoma/secuencias/nombre-Tratado5_Read1.fastq
metagenoma/secuencias/nombre-Tratado6_Read1.fastq
metagenoma/secuencias/nombre-Tratado7_Read1.fastq
metagenoma/secuencias/nombre-Tratado8_Read1.fastq
metagenoma/secuencias/nombre-Tratado9_Read1.fastq

Con pipes y variables, controlamos de manera flexible y poderosa los comandos que corremos en la terminal o en scripts. Prueba a anadir lo siguiente al final de bashscript.sh:

reads1=$(find metagenoma -type f -name "*_Read1.fastq*" | sort | paste -sd,)
reads2=$(find metagenoma -type f -name "*_Read2.fastq*" | sort | paste -sd,)

echo "Programa_para_reads_pareados -lista_de_read_1_separada_comma $reads1 -lista_de_reads2_separada_comma $reads2"
Programa_para_reads_pareados -lista_de_read_1_separada_comma  -lista_de_reads2_separada_comma 

Muchos programas que utlizaremos tienen esta estructura. Aunque es perfectamente posible escribir o hacer copy-paste para escribir el mismo comando (y de hecho asi lo haremos muchas veces en el curso para que sea mas claro lo que estamos haciendo), si algun dia te topas con que tienes que procesar cientos o miles de reads pareados, recuerda que existe una manera de hacerlo programaticamente.

./bashscript.sh "Jero"
Hola Jero, yo soy el programa ./bashscript.sh
1

Someter trabajos a KEN con Slurm

Finalmente, lo ultimo que necesitas para hacer bioinformatica aqui. En el cluster, rara vez corremos scripts directamente, sino que se los entregamos al administrador de colas slurm. Los scripts que le entregamos a slurm son basicamente scripts debash algo mas de informacion extra con los recursos que pedimos de RAM y CPUS, entre otras cosas. Copia lo siguiente en un archivo llamado cualquiernombre.slurm, modifica -J my_job , tu email y anade cualquera de los comandos que hemos visto al final del archivo.

#!/bin/bash
# Run job throughbash
#
# Your job name
#SBATCH -J first_slurm
#
# Use current working directory
#SBATCH --chdir=./
#
#stdout and stderr
#SBATCH --output=%x.o%j
#
# Send an email after the job has finished
#SBATCH --mail-user=jero.miranda.rod@gmail.com
#SBATCH --mail-type=END
#
# If modules are needed, source modules environment (Do not delete the next line):
. /etc/profile.d/modules.sh
#
# Add any modules you might require:

# 
#Quiero que el programa corra en dos nodos
#SBATCH -p node -n 2
#Cada nodo con 2G de ram
#SBATCH --mem-per-cpu=4G
#
# Write your commands in the next line
#A partir de aqui, puedes escribir los comandos

sbatch cualquiernombre.slurm

Listo!