Bash Tips Tricks Campus

813 views

Published on

Published in: Technology, News & Politics
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
813
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
0
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • macro processor means functionality where text and symbols are expanded to create larger expressions.
  • Bash Tips Tricks Campus

    1. 1. Uso avanzado de la línea de comandos Octavio H. Ruiz Cervera Campus Party 2009 México, D.F.
    2. 2. Patrocinan
    3. 3. Resumen La familiarización así como la productividad del usuario en un ambiente *NIX, están intrínsecamente relacionados con el conocimiento y aprovechamiento de las características de la interfaz básica que le otorga el sistema: La shell. Entre las características principales de Bash -el intérprete de comandos predeterminado de los sistemas GNU- se encuentran el control de trabajos; auto-completar; las expansiones (aritméticas, de variables, etc.); el historial; la edición y reingreso de comandos; entre otras. Particularmente más allá de ser la capa entre usuario y núcleo del sistema operativo, así como de sus características en esta relación, Bash es un poderoso lenguaje de programación en el cual bajo el contexto de «lanzador de programas», prácticamente cualquier utilidad o herramienta del repertorio de comandos UNIX, puede ser invocada desde un programa de shell, facilitando tareas de administración del sistema y trabajos repetitivos de rutina. Esta presentación proveerá al asistente una guía fácil para aprovechar las principales características de Bash como lenguaje de programación y lanzador de programas en el uso diario de sistemas *NIX.
    4. 4. Introducción <ul><ul><li>¿Qué aprenderemos aquí? </li></ul></ul><ul><ul><li>Utilizar el intérprete de comandos </li></ul></ul><ul><ul><li>Agilizar la introducción de comandos </li></ul></ul><ul><ul><li>Automatizar tareas </li></ul></ul><ul><ul><li>Tips y trucos </li></ul></ul><ul><ul><li>Más tips y trucos </li></ul></ul><ul><ul><li>¿Qué no aprenderemos aquí? </li></ul></ul><ul><ul><li>Uso de herramientas tipo Midnight Comadre </li></ul></ul>
    5. 5. Bash <ul><li>¿Qué es Bash? </li></ul><ul><ul><li>Acrónimo: Bourn Again Shell </li></ul></ul><ul><ul><li>Intérprete de comandos de facto </li></ul></ul><ul><ul><ul><li>Procesador de macros </li></ul></ul></ul><ul><ul><ul><li>Lanzador de programas </li></ul></ul></ul><ul><ul><li>Lenguaje de programación </li></ul></ul><ul><ul><ul><li>«No hay lenguaje de programación perfecto. No hay ni si quiera un mejor lenguaje; solo hay lenguajes buenos o quizás malos para una tarea en particular.» Herbert Mayer </li></ul></ul></ul><ul><li>Stephen Bourne fué el creador de la shell sh </li></ul>
    6. 6. Bash <ul><li>Cuándo no deberíamos usar Bash como lenguaje: </li></ul><ul><ul><li>Operaciones demandantes de recursos </li></ul></ul><ul><ul><li>Aritmética de punto flotante, números complejos </li></ul></ul><ul><ul><li>Misión crítica </li></ul></ul><ul><ul><li>Multiplataforma </li></ul></ul><ul><ul><li>Operadores de archivos (serial, linea por linea) </li></ul></ul><ul><ul><li>Soporte nativo de arreglos multidimensionales </li></ul></ul><ul><ul><li>Estructuras de datos </li></ul></ul><ul><ul><li>E/S </li></ul></ul><ul><ul><li>Seguridad </li></ul></ul>
    7. 7. Bash <ul><li>Fácil de aprender </li></ul><ul><ul><li>Pequeño conjunto de operadores y opciones </li></ul></ul><ul><ul><li>Sintaxis simple y directa </li></ul></ul><ul><ul><li>Documentación extensa y técnica </li></ul></ul><ul><li>Esencial para la administración de sistemas </li></ul><ul><ul><li>¿Sólo para la administración de sistemas? </li></ul></ul><ul><li>Método rápido para el prototipo de aplicaciones </li></ul><ul><li>Filosofía UNIX </li></ul>
    8. 8. Descriptores de archivos <ul><li>Tres descriptores básicos </li></ul><ul><ul><li>0 Entrada Estándar </li></ul></ul><ul><ul><li>1 Salida Estándar </li></ul></ul><ul><ul><li>2 Error Estándar </li></ul></ul><ul><li>Crear (abrir), redirigir, añadir, duplicar, mover, cerrar </li></ul><ul><li>Límite de 9 descriptores de archivos </li></ul>
    9. 9. Redirecciones y tuberías <ul><li>> </li></ul><ul><li>>> </li></ul><ul><li>< </li></ul><ul><li><<DELIMITADOR </li></ul><ul><li>| </li></ul>[email_address] ~ $ comando > archivo [email_address] ~ $ comando | comando [email_address] ~ $ comando 2> archivo [email_address] ~ $ comando 1> archivo 2>&1 [email_address] ~ $ comando &> archivo [email_address] ~ $ comando >> archivo [email_address] ~ $ comando <<EOF > archivo .... EOF
    10. 10. No clobber set -o noclobber [email_address] ~ $ set -o noclobber [email_address] ~ $ ls > archivo archivo: ASCII text [email_address] ~ $ ls > archivo -bash: archivo: cannot overwrite existing file [email_address] ~ $ ls >| archivo
    11. 11. FIFO's Substitución de procesos <ul><ul><li>Named Pipe </li></ul></ul><ul><ul><ul><li>First In, First Out (FIFO) </li></ul></ul></ul>tacvbo ~ $ mkfifo ELFIFO tacvbo ~ $ gzip -9 -c < ELFIFO > archivo.gzip tacvbo ~ $ cat archivo > ELFIFO tacvbo ~ $ tacvbo ~ $ diff -u <(wget http://web/archivo1) <(ssh cuatrocaminos 'cat archivo2') --- /dev/fd/63 2005-09-29 16:02:41.117475250 -0500 +++ /dev/fd/62 2005-09-29 16:02:41.117475250 -0500 @@ ....
    12. 12. Expansiones Brace <ul><ul><li>Generación arbitraria de cadenas </li></ul></ul>tacvbo ~ $ touch test-v{1,2,3} test-v1 test-v2 test-v3 tacvbo ~ $ ls /usr/{,local/}{,s}bin/binario /usr/bin/file /usr/sbin/file /usr/local/bin/file /usr/local/sbin/file
    13. 13. Loops <ul><li>for </li></ul><ul><ul><li>for (( i=10; i>0; ++i)) </li></ul></ul><ul><ul><li>for i in {1..10} </li></ul></ul><ul><ul><li>for i in 1 2 3 4 5 6 7 8 9 10 </li></ul></ul><ul><li>while </li></ul><ul><li>until </li></ul><ul><li>... </li></ul>
    14. 14. Variables de entorno <ul><li>Cadenas ASCII definidas por el usuario </li></ul><ul><li>Son parte estándar del modelo de procesos UNIX </li></ul><ul><ul><li>Cualquier programa ejecutado tendrá acceso a ellas </li></ul></ul><ul><ul><li>QUOTING: “” '' `` </li></ul></ul>[email_address] ~ $ VARIABLE=”hola a todos” [email_address] ~ $ VARIABLE=”hola a todos” [email_address] valancha ~ $ echo ${VARIABLE} hola a todos [email_address] ~ $ [email_address] ~ $ VARIABLE=”hola a todos” [email_address] ~ $ echo ${VARIABLE} hola a todos [email_address] ~ $ bash -c 'echo ${VARIABLE}' [email_address] ~ $ [email_address] ~ $ VARIABLE=”hola a todos” [email_address] ~ $ echo ${VARIABLE} hola a todos [email_address] ~ $ bash -c 'echo ${VARIABLE}' [email_address] ~ $ export VARIABLE [email_address] ~ $ [email_address] ~ $ VARIABLE=”hola a todos” [email_address] ~ $ echo ${VARIABLE} hola a todos [email_address] ~ $ bash -c 'echo ${VARIABLE}' [email_address] ~ $ export VARIABLE [email_address] ~ $ !b [email_address] ~ $ VARIABLE=”hola a todos” [email_address] ~ $ echo ${VARIABLE} hola a todos [email_address] ~ $ bash -c 'echo ${VARIABLE}' [email_address] ~ $ export VARIABLE [email_address] ~ $ bash -c 'echo ${VARIABLE}' hola a todos [email_address] ~ $
    15. 15. Cortando cadenas <ul><li>Dividir una cadena en partes (chunks) </li></ul><ul><ul><li>basename </li></ul></ul><ul><ul><li>dirname </li></ul></ul>[email_address] ~ $ basename /usr/pakistan/bin/laden laden [email_address] ~ $ dirname /usr/pakistan/bin/laden /usr/pakistan/bin [email_address] ~ $ <ul><ul><li>Esto es exclusivamente manipulación de cadenas </li></ul></ul>
    16. 16. Substitución de comandos <ul><li>Substituye la salida de un comando </li></ul><ul><ul><li>$(comando) </li></ul></ul><ul><ul><li>`comando` </li></ul></ul><ul><li>Esta substitución la podemos meter en una variable </li></ul><ul><ul><li>variable=$(comando) </li></ul></ul>tacvbo ~ $ MYFILE=$(basename /usr/pakistan/bin/laden) tacvbo ~ $ echo ${MYFILE} laden tacvbo ~ $ MYDIR=$(dirname /usr/pakistan/bin/laden) tacvbo ~ $ echo ${MYDIR} /usr/pakistan/bin/laden
    17. 17. Expansión de parámetros <ul><li>$ Invoca a la expansión de parámetros </li></ul><ul><ul><li>${VARIABLE} </li></ul></ul><ul><ul><li>${VARIABLE/patrón/cadena} </li></ul></ul><ul><ul><li>${VARIABLE:/desde (#)/cuantos (#)} </li></ul></ul><ul><ul><li>${VARIABLE##/*token} </li></ul></ul><ul><ul><li>${VARIABLE%/token*} </li></ul></ul><ul><ul><li>${VARIABLE:-token} </li></ul></ul><ul><ul><li>${VARIABLE:+token} </li></ul></ul>
    18. 18. Expansión de parámetros [email_address] ~ $ MYVAR=”hola a todos” [email_address] ~ $ echo ${MYVAR} hola a todos [email_address] ~ $ echo ${MYVAR/todos/tod@s} hola a tod@s [email_address] ~ $ MYVAR=abcdefghijklmn [email_address] ~ $ echo ${MYVAR} abcdefghijklmn [email_address] ~ $ echo ${MYVAR:3} defghijklmn [email_address] ~ $ echo ${MYVAR:3:3} def
    19. 19. Expansión de parámetros (Cortando Cadenas 2da parte) [email_address] ~ $ MYVAR=superfestivalfeliz [email_address] ~ $ echo ${MYVAR} superfestivalfeliz [email_address] ~ $ echo ${MYVAR##*fe} liz [email_address] ~ $ echo ${MYVAR#*fe} stivalfeliz [email_address] ~ $ MYVAR=superfestivalfeliz [email_address] ~ $ echo ${MYVAR} superfestivalfeliz [email_address] ~ $ echo ${MYVAR%%fe*} super [email_address] ~ $ echo ${MYVAR%fe*} superfestival
    20. 20. Expansión de parámetros (Cortando Cadenas 2da parte) superfestivalfeli z superfestivalfel iz superfestivalfe liz ## superfestivalf eliz superfestival feliz % superfestiva lfeliz superfestiv alfeliz superfesti valfeliz superfest ivalfeliz superfes tivalfeliz superfe stivalfeliz # superf estivalfeliz super festivalfeliz %% sup erfestivalfeliz su perfestivalfeliz s uperfestivalfeliz
    21. 21. Expansión de parámetros (Cortando Cadenas 2da parte) [email_address] ~ $ MYDIR=”/usr/pakistan/bin/laden” [email_address] ~ $ echo ${MYDIR} /usr/pakistan/bin/laden [email_address] ~ $ basename ${MYDIR} laden [email_address] ~ $ echo ${MYDIR##*/} laden [email_address] ~ $ dirname ${MYDIR} /usr/pakistan/bin [email_address] ~ $ echo ${MYDIR%/*} /usr/pakistan/bin [email_address] ~ $ MYDIR=${MYDIR%/*}
    22. 22. Expansión de parámetros tacvbo ~ $ VARIABLE=&quot;&quot; tacvbo ~ $ echo $VARIABLE tacvbo ~ $ echo ${VARIABLE:-Sin definir, SUBSTITUYO} Sin definir, SUBSTITUYO tacvbo ~ $ echo ${VARIABLE:?Sin definir, ERROR} -bash: VARIABLE: Sin definir, ERROR tacvbo ~ $ echo ${VARIABLE:+Sin definir, NO HAGO NADA} tacvbo ~ $ echo ${VARIABLE:=Sin definir, SETEO y SUBSTITUYO} Sin definir, SETEO y SUBSTITUYO tacvbo ~ $ echo ${VARIABLE} Sin definir, SETEO y SUBSTITUYO tacvbo ~ $ echo ${VARIABLE:+Definida, SUBSTITUYO} Definida, SUBSTITUYO
    23. 23. shopt <ul><li>Cambia los valores de variables que controlan el comportamiento de la shell </li></ul><ul><ul><li>-s (define) </li></ul></ul><ul><ul><li>-u (desactiva) </li></ul></ul><ul><ul><li>-q (modo silencioso) </li></ul></ul>
    24. 24. Coincidencia de patrones <ul><li>?(pattern-list) </li></ul><ul><ul><li>cero o una ocurrencia </li></ul></ul><ul><li>*(pattern-list) </li></ul><ul><ul><li>cero o más ocurrencias </li></ul></ul><ul><li>+(pattern-list) </li></ul><ul><ul><li>una o más ocurrencias </li></ul></ul>shopt -s extglob [email_address] ~ $ shopt -e extglob [email_address] ~ $ ls -lad !(*.mp?(eg|g)) uno.txt dos.pl tres.ext cuatro.sh [email_address] ~ $ <ul><li>@(pattern-list) </li></ul><ul><ul><li>Patrón exacto </li></ul></ul><ul><li>!(pattern-list) </li></ul><ul><ul><li>Todo excepto patrón </li></ul></ul>
    25. 25. Comandos compuestos <ul><li>( comando ) </li></ul><ul><li>{ comando1 ; comando2; comando3 } </li></ul><ul><li>(( expresión aritmética )) </li></ul><ul><li>[[ expresión condicional ]] </li></ul><ul><ul><li>! && || </li></ul></ul>
    26. 26. Expansión aritmética <ul><li>$(( )) </li></ul><ul><ul><li>id++ ++id </li></ul></ul><ul><ul><li>id-- --id </li></ul></ul><ul><ul><li>+ - ** * / </li></ul></ul>[email_address] ~ $ echo $(( 5 + 5 )) 10 [email_address] ~ $ n=2; echo $(( ++n )) 3 [email_address] ~ $
    27. 27. Control de tareas <ul><li>bg </li></ul><ul><li>fg </li></ul>root ~ $ updatedb ^Z [1]+ Stopped updatedb root ~ $ fg %1 updatedb ^Z [1]+ Stopped updatedb root ~ $ bg %1 [1]+ updatedb & root ~ $ jobs [1]+ Running updatedb & root ~ $ disown %1 [email_address] ~ $ ps aux | grep updatedb root 1439 2.0 0.1 7140 1056 pts/2 R 04:43 0:02 updatedb <ul><li>jobs </li></ul><ul><li>disown </li></ul>
    28. 28. Funciones <ul><li>Es un objeto que es llamado con un simple comando </li></ul><ul><li>Se puede exportar a nuestro ambiente (export) </li></ul><ul><ul><li>Sobreescribir el comportamiento de un comando </li></ul></ul><ul><ul><li>Crear nuevos comandos </li></ul></ul>
    29. 29. Funciones <ul><li>Ejemplos: </li></ul>function duff() { diff -ur “$@” } export -f duff function gimp() { command gimp “$@” &>/dev/null & } export -f duff
    30. 30. Funciones :(){:|:&};: :() { :|:& } : funcion() { funcion|funcion& } funcion <ul><li>Démosle un vistazo a esto: </li></ul><ul><li>Ahora identado... </li></ul><ul><li>Substituyamos... </li></ul><ul><ul><li>¡No es un BUG! </li></ul></ul>
    31. 31. ulimit <ul><li>Provee control sobre los recursos disponibles </li></ul><ul><ul><li>-c máximo tamaño de core dumps </li></ul></ul><ul><ul><li>-u máximo número de procesos al usuario </li></ul></ul><ul><ul><li>-a muestra todos los limites </li></ul></ul>[email_address] ~ $ ulimit -a | grep processes max user processes (-u) 8191 [email_address] ~ $ ulimit -u 128 [email_address] ~ $ ulimit -a | grep processes max user processes (-u) 128 [email_address] ~ $ :(){:|:&};: -bash: fork: Resource temporarily unavailable -bash: fork: Resource temporarily unavailable [email_address] ~ $
    32. 32. Grep export GREP_COLOR=31 export GREP_OPTIONS=&quot;--colour=auto --binary-files=text&quot; [email_address] ~ $ cat archivo blanco blanco blanco blanco blanco blanco blanco rojo blanco blanco blanco blanco blanco blanco blanco [email_address] ~ $ grep rojo archivo blanco blanco rojo blanco blanco [email_address] ~ $
    33. 33. Readline... a la emacs <ul><li>^a ^e </li></ul><ul><li>^f ^b </li></ul><ul><li>^l </li></ul><ul><li>M - m _ </li></ul><ul><li>^t M t </li></ul><ul><li>M u M l </li></ul><ul><li>^u ^k </li></ul><ul><li>^y </li></ul><ul><li>^ d </li></ul>export IGNOREEOF=1
    34. 34. ...O a la VI <ul><li>...o usar los keybindings de VI </li></ul><ul><ul><li>Editemos nuestro ${HOME}/.inputrc </li></ul></ul>set editing-mode vi set keymap vi [email_address] ~ $ set -o vi
    35. 35. Cambiando de directorio <ul><li>$CDPATH </li></ul><ul><ul><li>Variable del entorno </li></ul></ul><ul><ul><li>Separada por “dos puntos” </li></ul></ul><ul><ul><li>Añade a la lista del built-in `cd` </li></ul></ul><ul><li>Recordando donde hemos estado </li></ul><ul><ul><li>pushd </li></ul></ul><ul><ul><li>popd </li></ul></ul><ul><ul><li>dirs </li></ul></ul><ul><ul><li>cd -, ~- </li></ul></ul>
    36. 36. cdSpell <ul><li>Cuando escribimos incorrectamente el nombre de un directorio y no hay otra coincidencia, bash corregirá nuestro error. </li></ul>[email_address] ~ $ shopt -s cdspell
    37. 37. No mas dobles «tabs» <ul><li>Cuando presionamos «tab», y una ambigüedad es encontrada, es necesario pulsar nuevamente «tab» </li></ul><ul><li>Para evitar esto, y que solo un «tab» sea necesario: </li></ul><ul><ul><li>Editemos nuestro ${HOME}/.inputrc </li></ul></ul>set show-all-if-ambiguous on
    38. 38. ls -F al autocompletar <ul><li>En una terminal sin color, un ls -F es muy útil y podemos integrar este formato al autocompletar. </li></ul><ul><ul><li>Editemos nuestro ${HOME}/.inputrc </li></ul></ul>set visible-stats on [email_address] ~ $ ls -F archivo directorio/ ejecutable* fifo| symlink@ [email_address] ~ $ ls «tab» archivo directorio ejecutable fifo symlink [email_address] ~ $ set visible-stats on [email_address] ~ $ ls «tab» archivo directorio/ ejecutable* fifo| symboliclink@ [email_address] ~ $
    39. 39. Historial <ul><li>¿Para qué nos sirve el historial? </li></ul><ul><ul><li>Para no volver a escribir algo ya escrito </li></ul></ul><ul><ul><li>¿En verdad? </li></ul></ul><ul><ul><ul><li>Solo en teoría </li></ul></ul></ul><ul><ul><ul><li>Algunos comandos no son salvados </li></ul></ul></ul><ul><ul><ul><li>No explotamos su funcionalidad </li></ul></ul></ul><ul><ul><ul><li>UP y DOWN no es la manera mas eficiente </li></ul></ul></ul><ul><ul><ul><li>UP, LEFT y Backspace tampoco lo es </li></ul></ul></ul>
    40. 40. Historial <ul><li>El problema de la «Nueva Terminal» </li></ul>[email_address] ~ $ <up> rm -rf ${HOME}/tmp [email_address] ~ $ history | wc -l 101 [email_address] ~ $ <99 comandos después...> [email_address] ~ $ history | wc -l 200 [email_address] ~ $ ssh callampa.tacvbo.net Password: ******* [email_address] ~ $ [email_address] ~ $ <up> rm -rf ${HOME}/tmp [email_address] ~ $ history | wc -l 101 [email_address] ~ $
    41. 41. Historial <ul><li>El problema de la «Nueva Terminal» </li></ul>[email_address] ~ $ ssh callampa.tacvbo.net Password: ******* [email_address] ~ $ logout [email_address] ~ $ history | wc -l 201 [email_address] ~ $ logout [email_address] ~ $ history | wc -l 101 [email_address] ~ $ logout [email_address] ~ $ <up> rm -rf ${HOME}/tmp [email_address] ~ $ history | wc -l 101 [email_address] ~ $
    42. 42. Historial <ul><li>Al salir nuestro historial se guarda en ${HISTFILE} </li></ul><ul><li>Ambos problemas pueden ser solucionados añadiendo cada comando al historial en lugar de sobreescribirlo </li></ul><ul><ul><li>Editemos nuestro ${HOME}/.bashrc </li></ul></ul><ul><li>Si ya esta definida ${PROMPT_COMMAND} será sobreescrita </li></ul>PROMPT_COMMAND= &quot;${PROMPT_COMMAND:+${PROMPT_COMMAND} ;} history -a&quot; shopt -s histappend export PROMPT_COMMAND=”history -a” <ul><li>Si nuestra shell esta configurada como «login shell» el archivo que será leído es: .bash_profile </li></ul>
    43. 43. Buscando el pasado <ul><li>Las formas MUY poco efectivas: </li></ul><ul><ul><li>Presionar <UP> </li></ul></ul><ul><ul><li>Volver a presionar <UP> </li></ul></ul><ul><ul><li>Seguir presionando <UP> </li></ul></ul><ul><li>Las formas útiles, pero poco efectivas: </li></ul><ul><ul><li>Búsquedas incrementales con ^R y ^S </li></ul></ul><ul><li>Las formas rápidas, pero poco precisas: </li></ul><ul><ul><li>!comando </li></ul></ul>
    44. 44. Buscando en el pasado <ul><li>Búsquedas no incrementales con <UP> y <DOWN> </li></ul><ul><ul><li>Editemos nuestro ${HOME}/.inputrc </li></ul></ul>&quot;e[A&quot;: history-search-backward &quot;e[B&quot;: history-search-forward <ul><li>El antiguo comportamiento aun persiste en ^P y ^N previous-history y next-history respectivamente. </li></ul>
    45. 45. Historial (!!) <ul><li>Ultimo argumento !$ </li></ul><ul><li>Todos los argumentos !* </li></ul><ul><li>Substituciones ^patron^string </li></ul><ul><li>Substituciones !:gs/patron/string </li></ul><ul><li>Comando anterior ! </li></ul><ul><li>N comandos anteriores !-n </li></ul><ul><li>:h (dirname) :t (basename) </li></ul><ul><li>:r </li></ul>
    46. 46. Historial <ul><li>Podemos saber que sucederá en nuestra substitución </li></ul><ul><ul><li>Usando el «espacio mágico» </li></ul></ul><ul><ul><li>Usando histverify en nuestras opciones de shell </li></ul></ul><ul><ul><li>:p no solo es una carita sacando la lengua </li></ul></ul>
    47. 47. El espacio mágico <ul><li>El espacio mágico (magic-space) </li></ul><ul><ul><li>Editemos nuestro ${HOME}/.inputrc </li></ul></ul>Space: magic-space
    48. 48. histverify <ul><li>histverify pondrá en el buffer de edición la substitución antes de que sea pasada a la shell </li></ul><ul><ul><li>Editemos nuestro ${HOME}/.bashrc </li></ul></ul>shopt -s histverify
    49. 49. :P no es una carita <ul><li>Lo podemos usar en lugar de histverify </li></ul><ul><li>No podemos editar la salida </li></ul><ul><ul><li>Añadamos :p al final de nuestra expresión </li></ul></ul>[email_address] ~ $ history | wc -l 101 [email_address] ~ $ !h:p history | wc -l [email_address] ~ $
    50. 50. ¿Preguntas? <ul><li>? </li></ul>
    51. 51. Contacto <ul><li>Octavio H. Ruiz (Ta^3) </li></ul><ul><li>[email_address] </li></ul><ul><li>http://www.tacvbo.net/ </li></ul><ul><li>http://www.twitter.com/tacvbo </li></ul>
    52. 52. Layout

    ×