Content from Introducción al shell


Última actualización: 2025-04-03 | Mejora esta página

Hoja de ruta

Preguntas

  • ¿Qué es un intérprete de comandos y por qué debería utilizarlo?

Objetivos

  • Explicar cómo se relaciona el shell con el teclado, la pantalla, el sistema operativo y los programas de los usuarios.
  • Explicar cuándo y por qué deben utilizarse interfaces de línea de comandos en lugar de interfaces gráficas.

Antecedentes

Los humanos y los ordenadores suelen interactuar de muchas formas diferentes, como a través de un teclado y un ratón, interfaces de pantalla táctil o utilizando sistemas de reconocimiento de voz. La forma más utilizada para interactuar con los ordenadores personales se denomina interfaz gráfica de usuario (GUI). Con una GUI, damos instrucciones haciendo clic con el ratón y utilizando interacciones basadas en menús.

Aunque la ayuda visual de una interfaz gráfica de usuario hace que el aprendizaje sea intuitivo, esta forma de dar instrucciones a un ordenador es muy poco escalable. Imagine la siguiente tarea: para una búsqueda bibliográfica, tiene que copiar la tercera línea de mil archivos de texto en mil directorios diferentes y pegarla en un único archivo. Si utilizara una interfaz gráfica de usuario, no sólo pasaría varias horas haciendo clic en su escritorio, sino que además podría cometer un error en el proceso de realización de esta tarea repetitiva. Aquí es donde aprovechamos las ventajas del shell de Unix. El shell de Unix es tanto una interfaz de línea de comandos (CLI) como un lenguaje de secuencias de comandos, que permite realizar estas tareas repetitivas de forma automática y rápida. Con los comandos adecuados, el shell puede repetir tareas con o sin alguna modificación tantas veces como queramos. Usando el shell, la tarea del ejemplo de la literatura puede realizarse en segundos.

El Shell

El shell es un programa en el que los usuarios pueden escribir comandos. Con el shell, es posible invocar programas complicados como el software de modelado climático o comandos sencillos que crean un directorio vacío con una sola línea de código. El intérprete de comandos Unix más popular es Bash (Bourne Again SHell, así llamado porque deriva de un intérprete de comandos escrito por Stephen Bourne). Bash es el shell por defecto en la mayoría de las implementaciones modernas de Unix y en la mayoría de los paquetes que proporcionan herramientas tipo Unix para Windows. Ten en cuenta que ‘Git Bash’ es un software que permite a los usuarios de Windows utilizar una interfaz similar a Bash cuando interactúan con Git.

Utilizar el shell requiere cierto esfuerzo y tiempo de aprendizaje. Mientras que una interfaz gráfica de usuario te presenta opciones para seleccionar, las opciones de la CLI no se te presentan automáticamente, por lo que deberás aprender algunos comandos como si se tratara de vocabulario nuevo en un idioma que estás estudiando. Sin embargo, a diferencia de un idioma hablado, un pequeño número de “palabras” (es decir, comandos) te lleva un largo camino, y vamos a cubrir esos pocos esenciales hoy.

La gramática de un shell permite combinar herramientas existentes en potentes pipelines y manejar grandes volúmenes de datos de forma automática. Las secuencias de comandos pueden escribirse en un script, lo que mejora la reproducibilidad de los flujos de trabajo.

Además, la línea de comandos suele ser la forma más sencilla de interactuar con máquinas remotas y superordenadores. La familiaridad con el shell es casi esencial para ejecutar una variedad de herramientas y recursos especializados, incluidos los sistemas informáticos de alto rendimiento. A medida que los clústeres y los sistemas de computación en nube se hacen más populares para el procesamiento de datos científicos, ser capaz de interactuar con el intérprete de comandos se está convirtiendo en una habilidad necesaria. Podemos basarnos en las habilidades de la línea de comandos que se tratan aquí para abordar una amplia gama de cuestiones científicas y retos computacionales.

Empecemos.

Cuando se abre el intérprete de comandos por primera vez, aparece un prompt, que indica que el intérprete está esperando una entrada.

BASH

$

El shell suele utilizar $ como prompt, pero puede utilizar un símbolo diferente. En los ejemplos de esta lección, mostraremos el prompt como $. Lo más importante, no escribas el prompt cuando escribas comandos. Sólo escriba el comando que sigue al prompt. Esta regla se aplica tanto en estas lecciones como en lecciones de otras fuentes. También tenga en cuenta que después de escribir un comando, tiene que pulsar la tecla Enter para ejecutarlo.

El prompt va seguido de un cursor de texto, un carácter que indica la posición en la que aparecerá lo que escribas. El cursor suele ser un bloque parpadeante o sólido, pero también puede ser un guión bajo o una tubería. Es posible que lo hayas visto en un programa editor de texto, por ejemplo.

Ten en cuenta que tu prompt puede tener un aspecto algo diferente. En particular, los entornos de shell más populares ponen por defecto su nombre de usuario y el nombre del host antes de $. Un prompt de este tipo podría tener, por ejemplo, el aspecto siguiente

BASH

nelle@localhost $

El prompt puede incluir incluso más que esto. No se preocupe si su prompt no es sólo un corto $. Esta lección no depende de esta información adicional y tampoco debería ser un obstáculo. El único elemento importante es el carácter $ y veremos más adelante por qué.

Así que vamos a probar nuestro primer comando, ls, que es la abreviatura de listado. Este comando listará el contenido del directorio actual:

BASH

$ ls

SALIDA

Desktop     Downloads   Movies      Pictures
Documents   Library     Music       Public

Comando no encontrado

Si el shell no puede encontrar un programa cuyo nombre sea el comando que has escrito, imprimirá un mensaje de error como:

BASH

$ ks

SALIDA

ks: command not found

Esto puede ocurrir si el comando se ha escrito mal o si el programa correspondiente a ese comando no está instalado.

Nelle’s Pipeline: Un problema típico


Nelle Nemo, bióloga marina, acaba de regresar de un estudio de seis meses en el Giro del Pacífico Norte, donde ha estado tomando muestras de vida marina gelatinosa en el Gran Parche de Basura del Pacífico. Tiene 1520 muestras que ha pasado por una máquina de ensayo para medir la abundancia relativa de 300 proteínas. Tiene que pasar estos 1520 archivos por un programa imaginario llamado goostats.sh. Además de esta ingente tarea, tiene que escribir los resultados antes de fin de mes, para que su artículo pueda aparecer en un número especial de Aquatic Goo Letters.

Si Nelle elige ejecutar goostats.sh a mano utilizando una GUI, tendrá que seleccionar y abrir un archivo 1520 veces. Si goostats.sh tarda 30 segundos en ejecutar cada archivo, todo el proceso requerirá más de 12 horas de la atención de Nelle. Con el intérprete de comandos, Nelle puede asignar a su ordenador esta tarea mundana mientras se concentra en escribir su trabajo.

Las próximas lecciones explorarán las formas en que Nelle puede conseguirlo. Más concretamente, las lecciones explican cómo puede utilizar un intérprete de comandos para ejecutar el programa goostats.sh, utilizando bucles para automatizar los pasos repetitivos de introducción de nombres de archivo, de modo que su ordenador pueda trabajar mientras ella escribe su trabajo.

Como ventaja adicional, una vez que haya creado un canal de procesamiento, podrá volver a utilizarlo siempre que recopile más datos.

Para realizar su tarea, Nelle necesita saber cómo:

  • navegar a un archivo/directorio
  • crear un archivo/directorio
  • comprobar la longitud de un archivo
  • encadenar comandos
  • recuperar un conjunto de archivos
  • iterar sobre archivos
  • ejecutar un script de shell que contenga su pipeline

Puntos Clave

  • Un shell es un programa cuyo propósito principal es leer comandos y ejecutar otros programas.
  • Esta lección utiliza Bash, el shell por defecto en muchas implementaciones de Unix.
  • Los programas pueden ejecutarse en Bash introduciendo comandos en el prompt de la línea de comandos.
  • Las principales ventajas del intérprete de comandos son su elevada relación entre acciones y pulsaciones, su capacidad para automatizar tareas repetitivas y su capacidad para acceder a máquinas en red.
  • Un reto importante a la hora de utilizar el shell puede ser saber qué comandos hay que ejecutar y cómo ejecutarlos.

Content from Navegación por archivos y directorios


Última actualización: 2025-04-03 | Mejora esta página

Hoja de ruta

Preguntas

  • ¿Cómo puedo moverme por mi ordenador?
  • ¿Cómo puedo ver qué ficheros y directorios tengo?
  • ¿Cómo puedo especificar la ubicación de un archivo o directorio en mi ordenador?

Objetivos

  • Explique las similitudes y diferencias entre un fichero y un directorio.
  • Convierte una ruta absoluta en relativa y viceversa.
  • Construye rutas absolutas y relativas que identifican archivos y directorios específicos.
  • Usar opciones y argumentos para cambiar el comportamiento de un comando del shell.
  • Demuestre el uso del tabulador y explique sus ventajas.

La parte del sistema operativo responsable de la gestión de ficheros y directorios se denomina sistema de ficheros. Organiza nuestros datos en ficheros, que contienen información, y directorios (también llamados “carpetas”), que contienen ficheros u otros directorios.

Hay varios comandos que se usan frecuentemente para crear, inspeccionar, renombrar y borrar archivos y directorios. Para empezar a explorarlos, iremos a nuestra ventana de shell abierta.

Primero, averigüemos dónde estamos ejecutando un comando llamado pwd (que significa ‘imprimir directorio de trabajo’). Los directorios son como lugares - en cualquier momento mientras usamos el shell, estamos exactamente en un lugar llamado nuestro directorio de trabajo actual. La mayoría de los comandos leen y escriben archivos en el directorio de trabajo actual, es decir, “aquí”, por lo que es importante saber dónde nos encontramos antes de ejecutar un comando.pwd te muestra dónde estás:

BASH

$ pwd

SALIDA

/Users/nelle

Aquí, la respuesta del ordenador es /Users/nelle, que es el directorio home de Nelle:

Variación del directorio de inicio

La ruta del directorio de inicio tendrá un aspecto diferente en los distintos sistemas operativos. En Linux, puede parecerse a /home/nelle, y en Windows, será similar a C:\Documents and Settings\nelle o C:\Users\nelle. (Tenga en cuenta que puede tener un aspecto ligeramente diferente para las distintas versiones de Windows.) En futuros ejemplos, hemos utilizado la salida de Mac por defecto - la salida de Linux y Windows puede diferir ligeramente, pero en general debería ser similar.

También supondremos que su comando pwd devuelve el directorio personal de su usuario. Si pwd devuelve algo diferente, puede que necesite navegar hasta allí usando cd o algunos comandos de esta lección no funcionarán como están escritos. Ver Explorando otros directorios para más detalles sobre el comando cd.

Para entender qué es un “directorio personal”, veamos cómo está organizado el sistema de ficheros en su conjunto. En este ejemplo, ilustraremos el sistema de archivos del ordenador de nuestra científica Nelle. Después de esta ilustración, aprenderás comandos para explorar tu propio sistema de archivos, que estará construido de forma similar, pero no será exactamente idéntico.

En el ordenador de Nelle, el sistema de ficheros tiene el siguiente aspecto:

El sistema de ficheros está formado por un directorio raíz que contiene subdirectorios titulados bin, data, users y tmp

El sistema de ficheros parece un árbol al revés. El directorio superior es el directorio raíz que contiene todo lo demás. Nos referimos a él utilizando un carácter de barra oblicua, /, por sí solo; este carácter es la barra oblicua inicial en /Users/nelle.

Dentro de ese directorio hay otros directorios: bin (que es donde se almacenan algunos programas incorporados), data (para archivos de datos varios), Users (donde se encuentran los directorios personales de los usuarios), tmp (para archivos temporales que no necesitan almacenarse a largo plazo), etc.

Sabemos que nuestro directorio de trabajo actual /Users/nelle está almacenado dentro de /Users porque /Users es la primera parte de su nombre. Del mismo modo, sabemos que /Users está dentro del directorio raíz / porque su nombre empieza por /.

Barra inclinada

Tenga en cuenta que el carácter / tiene dos significados. Cuando aparece al principio del nombre de un fichero o directorio, se refiere al directorio raíz. Cuando aparece dentro de una ruta, es sólo un separador.

Debajo de /Users, encontramos un directorio para cada usuario con una cuenta en la máquina de Nelle, sus colegas imhotep y larry.

Como otros directorios, los directorios home son subdirectorios bajo "/Usuarios" como "/Usuarios/imhotep", "/Usuarios/larry" o "/Usuarios/nelle"

Los ficheros del usuario imhotep se almacenan en /Users/imhotep, los del usuario larry en /Users/larry, y los de Nelle en /Users/nelle. Nelle es el usuario de nuestros ejemplos; por lo tanto, tenemos /Users/nelle como nuestro directorio personal. Normalmente, cuando abres un nuevo símbolo del sistema, estarás en tu directorio de inicio para empezar.

Ahora vamos a aprender el comando que nos permitirá ver el contenido de nuestro propio sistema de ficheros. Podemos ver lo que hay en nuestro directorio home ejecutando ls:

BASH

$ ls

SALIDA

Applications Documents    Library      Music        Public
Desktop      Downloads    Movies       Pictures

(De nuevo, sus resultados pueden ser ligeramente diferentes dependiendo de su sistema operativo y de cómo haya personalizado su sistema de ficheros)

ls imprime los nombres de los ficheros y directorios del directorio actual. Podemos hacer que su salida sea más comprensible utilizando la opción -F que indica a ls que clasifique la salida añadiendo un marcador a los nombres de archivos y directorios para indicar cuáles son:

  • un / al final indica que se trata de un directorio
  • @ indica un enlace
  • * indica un ejecutable

Dependiendo de la configuración por defecto de su shell, ésta también puede utilizar colores para indicar si cada entrada es un fichero o un directorio.

BASH

$ ls -F

SALIDA

Applications/ Documents/    Library/      Music/        Public/
Desktop/      Downloads/    Movies/       Pictures/

Aquí, podemos ver que el directorio home contiene sólo sub-directorios. Cualquier nombre en la salida que no tenga un símbolo de clasificación son archivos en el directorio de trabajo actual.

Borrando tu terminal

Si su pantalla está demasiado llena, puede limpiar su terminal utilizando el comando clear. Todavía puedes acceder a comandos anteriores utilizando y para moverte línea a línea, o desplazándote por tu terminal.

Obtener ayuda

ls tiene muchas otras opciones. Hay dos formas comunes de averiguar cómo usar un comando y qué opciones acepta — dependiendo de tu entorno, puede que sólo una de estas formas funcione:

  1. Podemos pasar una opción --help a cualquier comando (disponible en Linux y Git Bash), por ejemplo:

BASH

$ ls --help
  1. Podemos leer su manual con man (disponible en Linux y macOS):

BASH

$ man ls

A continuación describiremos ambas formas.

Ayuda para comandos incorporados

Algunos comandos están integrados en el shell Bash, en lugar de existir como programas separados en el sistema de ficheros. Un ejemplo es el comando cd (cambiar directorio). Si recibes un mensaje como No manual entry for cd, prueba con help cd en su lugar. El comando help es la forma de obtener información de uso de Bash built-ins.

La opción --help

La mayoría de los comandos bash y programas que la gente ha escrito para ser ejecutados desde bash, soportan una opción --help que muestra más información sobre cómo usar el comando o programa.

BASH

$ ls --help

SALIDA

Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if neither -cftuvSUX nor --sort is specified.

Mandatory arguments to long options are mandatory for short options, too.
  -a, --all                  do not ignore entries starting with .
  -A, --almost-all           do not list implied . and ..
      --author               with -l, print the author of each file
  -b, --escape               print C-style escapes for nongraphic characters
      --block-size=SIZE      scale sizes by SIZE before printing them; e.g.,
                               '--block-size=M' prints sizes in units of
                               1,048,576 bytes; see SIZE format below
  -B, --ignore-backups       do not list implied entries ending with ~
  -c                         with -lt: sort by, and show, ctime (time of last
                               modification of file status information);
                               with -l: show ctime and sort by name;
                               otherwise: sort by ctime, newest first
  -C                         list entries by columns
      --color[=WHEN]         colorize the output; WHEN can be 'always' (default
                               if omitted), 'auto', or 'never'; more info below
  -d, --directory            list directories themselves, not their contents
  -D, --dired                generate output designed for Emacs' dired mode
  -f                         do not sort, enable -aU, disable -ls --color
  -F, --classify             append indicator (one of */=>@|) to entries
...        ...        ...

Cuándo usar opciones cortas o largas

Cuando las opciones existen como opciones cortas y largas:

  • Utilice la opción abreviada cuando escriba comandos directamente en el intérprete de comandos para minimizar las pulsaciones de teclas y realizar su tarea más rápidamente.
  • Utilice la opción larga en los scripts para proporcionar claridad. Se leerá muchas veces y se escribirá una vez.

Opciones de línea de órdenes no soportadas

Si intenta utilizar una opción que no está soportada, ls y otros comandos normalmente imprimirán un mensaje de error similar a:

BASH

$ ls -j

ERROR

ls: invalid option -- 'j'
Try 'ls --help' for more information.

El comando man

La otra forma de conocer ls es escribir

BASH

$ man ls

Este comando convertirá su terminal en una página con una descripción del comando ls y sus opciones.

Para navegar por las páginas man, puede utilizar y para desplazarse línea a línea, o pruebe con b y Barra espaciadora para saltar hacia arriba y hacia abajo una página completa. Para buscar un carácter o palabra en las páginas man, utilice / seguido del carácter o palabra que busca. A veces, una búsqueda dará como resultado varios resultados. Si es así, puede desplazarse entre los resultados utilizando N (para avanzar) y Mayús+N (para retroceder).

Para salir de las páginas man, pulse q.

Páginas del manual en la web

Por supuesto, hay una tercera forma de acceder a la ayuda para comandos: buscar en internet a través de su navegador web. Cuando utilices la búsqueda en Internet, incluir la frase unix man page en tu consulta de búsqueda te ayudará a encontrar resultados relevantes.

GNU proporciona enlaces a sus manuales incluyendo el núcleo de utilidades GNU, que cubre muchos comandos introducidos en esta lección.

Explorando más opciones ls

También puede utilizar dos opciones al mismo tiempo. ¿Qué hace el comando ls cuando se usa con la opción -l? ¿Y si utilizas tanto la opción -l como la opción -h?

Parte de su salida es sobre propiedades que no cubrimos en esta lección (como permisos y propiedad de archivos), pero el resto debería ser útil de todos modos.

La opción -l hace que ls utilice un formato de listado largo, mostrando no sólo los nombres de los ficheros/directorios sino también información adicional, como el tamaño del fichero y la hora de su última modificación. Si utiliza tanto la opción -h como la opción -l, esto hace que el tamaño del fichero sea ‘hman readable’, es decir, mostrando algo como 5.3K en lugar de 5369.

Listado en orden cronológico inverso

Por defecto, ls lista los contenidos de un directorio en orden alfabético por nombre. El comando ls -t lista los elementos por la hora de la última modificación en lugar de alfabéticamente. El comando ls -r lista los contenidos de un directorio en orden inverso. ¿Qué fichero se muestra en último lugar cuando se combinan las opciones -t y -r? Sugerencia: Puede que necesites usar la opción -l para ver las fechas de los últimos cambios.

El archivo modificado más recientemente aparece en último lugar cuando se utiliza -rt. Esto puede ser muy útil para encontrar las ediciones más recientes o comprobar si se ha escrito un nuevo archivo de salida.

Explorando Otros Directorios

No sólo podemos utilizar ls en el directorio de trabajo actual, sino que también podemos utilizarlo para listar el contenido de un directorio diferente. Echemos un vistazo a nuestro directorio Desktop ejecutando ls -F Desktop, es decir, el comando ls con la opción -F y el [argumento][Argumentos] Desktop. El argumento Desktop indica a ls que queremos un listado de algo distinto a nuestro directorio de trabajo actual:

BASH

$ ls -F Desktop

SALIDA

shell-lesson-data/

Tenga en cuenta que si un directorio llamado Desktop no existe en su directorio de trabajo actual, este comando devolverá un error. Típicamente, un directorio Desktop existe en su directorio home, que asumimos es el directorio de trabajo actual de su shell bash.

Su salida debería ser una lista de todos los archivos y subdirectorios en su directorio de Escritorio, incluyendo el directorio shell-lesson-data que descargó en el setup para esta lección. (En la mayoría de los sistemas, el contenido del directorio Desktop en el intérprete de comandos se mostrará como iconos en una interfaz gráfica de usuario detrás de todas las ventanas abiertas. Compruebe si éste es su caso)

Organizar las cosas jerárquicamente nos ayuda a llevar un control de nuestro trabajo. Aunque es posible poner cientos de archivos en nuestro directorio personal, igual que es posible apilar cientos de papeles impresos en nuestro escritorio, es mucho más fácil encontrar las cosas cuando se han organizado en subdirectorios con nombres sensatos.

Ahora que sabemos que el directorio shell-lesson-data se encuentra en nuestro directorio Escritorio, podemos hacer dos cosas.

Primero, usando la misma estrategia que antes, podemos mirar su contenido pasando un nombre de directorio a ls:

BASH

$ ls -F Desktop/shell-lesson-data

SALIDA

exercise-data/  north-pacific-gyre/

En segundo lugar, podemos cambiar nuestra ubicación a un directorio diferente, por lo que ya no estamos ubicados en nuestro directorio raíz.

El comando para cambiar de ubicación es cd seguido de un nombre de directorio para cambiar nuestro directorio de trabajo.cd significa “cambiar directorio”, lo que es un poco engañoso. El comando no cambia el directorio; cambia el directorio de trabajo actual del shell. En otras palabras, cambia la configuración de la shell para saber en qué directorio estamos. El comando cd es similar a hacer doble clic en una carpeta en una interfaz gráfica para entrar en esa carpeta.

Digamos que queremos movernos al directorio exercise-data que vimos anteriormente. Podemos utilizar la siguiente serie de comandos para llegar allí:

BASH

$ cd Desktop
$ cd shell-lesson-data
$ cd exercise-data

Estos comandos nos moverán desde nuestro directorio de inicio a nuestro directorio de Escritorio, luego al directorio shell-lesson-data, luego al directorio exercise-data. Notarás que cd no imprime nada. Esto es normal. Muchos comandos del shell no muestran nada en pantalla cuando se ejecutan correctamente. Pero si ejecutamos pwd después, podemos ver que ahora estamos en /Users/nelle/Desktop/shell-lesson-data/exercise-data.

Si ahora ejecutamos ls -F sin argumentos, nos muestra el contenido de /Users/nelle/Desktop/shell-lesson-data/exercise-data, porque ahí es donde estamos ahora:

BASH

$ pwd

SALIDA

/Users/nelle/Desktop/shell-lesson-data/exercise-data

BASH

$ ls -F

SALIDA

alkanes/  animal-counts/  creatures/  numbers.txt  writing/

Ya sabemos cómo bajar por el árbol de directorios (es decir, cómo entrar en un subdirectorio), pero ¿cómo subimos (es decir, cómo salimos de un directorio y entramos en su directorio padre)? Podríamos intentar lo siguiente:

BASH

$ cd shell-lesson-data

ERROR

-bash: cd: shell-lesson-data: No such file or directory

¡Pero obtenemos un error! ¿A qué se debe?

Con nuestros métodos hasta ahora, cd sólo puede ver subdirectorios dentro de su directorio actual. Hay diferentes maneras de ver los directorios por encima de su ubicación actual, vamos a empezar con el más simple.

Hay un atajo en el shell para subir un nivel de directorio. Funciona de la siguiente manera:

BASH

$ cd ..

.. es un nombre de directorio especial que significa “el directorio que contiene a éste”, o más sucintamente, el padre del directorio actual. Efectivamente, si ejecutamos pwd después de ejecutar cd .., volvemos a estar en /Users/nelle/Desktop/shell-lesson-data:

BASH

$ pwd

SALIDA

/Users/nelle/Desktop/shell-lesson-data

El directorio especial .. no suele aparecer cuando ejecutamos ls. Si queremos mostrarlo, podemos añadir la opción -a a ls -F:

BASH

$ ls -F -a

SALIDA

./  ../  exercise-data/  north-pacific-gyre/

-a significa ‘mostrar todo’ (incluyendo ficheros ocultos); fuerza a ls a mostrarnos nombres de ficheros y directorios que empiecen por ., como .. (que, si estamos en /Users/nelle, se refiere al directorio /Users). Como puedes ver, también muestra otro directorio especial que se llama simplemente ., que significa ‘el directorio de trabajo actual’. Puede parecer redundante tener un nombre para él, pero pronto le veremos algunos usos.

Tenga en cuenta que en la mayoría de las herramientas de línea de comandos, varias opciones se pueden combinar con una sola - y sin espacios entre las opciones; ls -F -a es equivalente a ls -Fa.

Otros ficheros ocultos

Además de los directorios ocultos .. y ., también puede ver un fichero llamado .bash_profile. Este archivo suele contener ajustes de configuración del shell. También puede ver otros ficheros y directorios que empiezan por .. Normalmente son ficheros y directorios que se utilizan para configurar diferentes programas en tu ordenador. El prefijo . se utiliza para evitar que estos ficheros de configuración saturen el terminal cuando se utiliza un comando estándar ls.

Estos tres comandos son los comandos básicos para navegar por el sistema de ficheros de tu ordenador: pwd, ls, y cd. Exploremos algunas variaciones de estos comandos. ¿Qué ocurre si escribes cd solo, sin dar un directorio?

BASH

$ cd

¿Cómo puedes comprobar qué ha pasado? ¡pwd nos da la respuesta!

BASH

$ pwd

SALIDA

/Users/nelle

Resulta que cd sin un argumento te devolverá a tu directorio home, lo cual es genial si te has perdido en tu propio sistema de ficheros.

Intentemos volver al directorio exercise-data de antes. La última vez, usamos tres comandos, pero en realidad podemos encadenar la lista de directorios para movernos a exercise-data en un solo paso:

BASH

$ cd Desktop/shell-lesson-data/exercise-data

Comprueba que nos hemos movido al lugar correcto ejecutando pwd y ls -F.

Si queremos subir un nivel desde el directorio de datos, podríamos usar cd ... Pero hay otra forma de moverse a cualquier directorio, independientemente de su ubicación actual.

Hasta ahora, al especificar nombres de directorio, o incluso una ruta de directorio (como arriba), hemos estado utilizando rutas relativas. Cuando se utiliza una ruta relativa con un comando como ls o cd, se intenta encontrar esa ubicación desde donde nos encontramos, en lugar de desde la raíz del sistema de ficheros.

Sin embargo, es posible especificar la ruta absoluta a un directorio incluyendo su ruta completa desde el directorio raíz, que se indica mediante una barra oblicua inicial. La barra / indica al ordenador que siga la ruta desde la raíz del sistema de ficheros, por lo que siempre se refiere exactamente a un directorio, independientemente de dónde nos encontremos cuando ejecutemos el comando.

Esto nos permite movernos a nuestro directorio shell-lesson-data desde cualquier parte del sistema de ficheros (incluso desde dentro de exercise-data). Para encontrar la ruta absoluta que estamos buscando, podemos usar pwd y luego extraer el trozo que necesitamos mover a shell-lesson-data.

BASH

$ pwd

SALIDA

/Users/nelle/Desktop/shell-lesson-data/exercise-data

BASH

$ cd /Users/nelle/Desktop/shell-lesson-data

Ejecuta pwd y ls -F para asegurarte de que estamos en el directorio que esperamos.

Dos atajos más

El shell interpreta que un carácter con tilde (~) al principio de una ruta significa “el directorio personal del usuario actual”. Por ejemplo, si el directorio personal de Nelle es /Users/nelle, entonces ~/data es equivalente a /Users/nelle/data. Esto sólo funciona si es el primer carácter de la ruta; here/there/~/elsewhere no es here/there/Users/nelle/elsewhere.

Otro atajo es el carácter - (guión).cd traducirá - a el directorio anterior en el que estaba, lo que es más rápido que tener que recordar, y luego teclear, la ruta completa. Esta es una forma muy eficiente de moverse hacia adelante y hacia atrás entre dos directorios – es decir, si ejecuta cd - dos veces, acabará de vuelta en el directorio inicial.

La diferencia entre cd .. y cd - es que el primero te lleva hacia arriba, mientras que el segundo te lleva hacia atrás.


¡Pruébelo! Primero navegue a ~/Desktop/shell-lesson-data (ya debería estar allí).

BASH

$ cd ~/Desktop/shell-lesson-data

Luego cd en el directorio exercise-data/creatures

BASH

$ cd exercise-data/creatures

Ahora si ejecuta

BASH

$ cd -

verás que estás de vuelta en ~/Desktop/shell-lesson-data. Ejecute cd - de nuevo y estará de vuelta en ~/Desktop/shell-lesson-data/exercise-data/creatures

Rutas absolutas vs relativas

Partiendo de /Users/nelle/data, ¿cuál de los siguientes comandos podría utilizar Nelle para navegar hasta su directorio personal, que es /Users/nelle?

  1. cd .
  2. cd /
  3. cd /home/nelle
  4. cd ../..
  5. cd ~
  6. cd home
  7. cd ~/data/..
  8. cd
  9. cd ..
  1. No: . representa el directorio actual.
  2. No: / representa el directorio raíz.
  3. No: El directorio personal de Nelle es /Users/nelle.
  4. No: este comando sube dos niveles, es decir, termina en /Users.
  5. Sí: ~ representa el directorio personal del usuario, en este caso /Users/nelle.
  6. No: este comando navegaría a un directorio home en el directorio actual si existe.
  7. Sí: innecesariamente complicado, pero correcto.
  8. Sí: acceso directo para volver al directorio personal del usuario.
  9. Sí: sube un nivel.

Resolución de la ruta relativa

Usando el siguiente diagrama del sistema de ficheros, si pwd muestra /Users/thing, ¿qué mostrará ls -F ../backup?

  1. ../backup: No such file or directory
  2. 2012-12-01 2013-01-08 2013-01-27
  3. 2012-12-01/ 2013-01-08/ 2013-01-27/
  4. original/ pnas_final/ pnas_sub/
Un árbol de directorios bajo el directorio Users donde "/Users" contiene los directorios "backup" y "thing"; "/Users/backup" contiene "original", "pnas_final" y "pnas_sub"; "/Users/thing" contiene "backup"; y"/Users/thing/backup" contiene "2012-12-01", "2013-01-08" y "2013-01-27"
  1. No: existe *un directorio backup en /Users.
  2. No: este es el contenido de Users/thing/backup, pero con .., pedimos un nivel más arriba.
  3. No: ver explicación anterior.
  4. Sí: ../backup/ se refiere a /Users/backup/.

ls Comprensión lectora

Usando el diagrama del sistema de ficheros de abajo, si pwd muestra /Users/backup, y -r le dice a ls que muestre las cosas en orden inverso, qué comando(s) resultará(n) en la siguiente salida:

SALIDA

pnas_sub/ pnas_final/ original/
Un árbol de directorios bajo el directorio Users donde "/Users" contiene los directorios "backup" y "thing"; "/Users/backup" contiene "original", "pnas_final" y "pnas_sub"; "/Users/thing" contiene "backup"; y"/Users/thing/backup" contiene "2012-12-01", "2013-01-08" y "2013-01-27"
  1. ls pwd
  2. ls -r -F
  3. ls -r -F /Users/backup
  1. No: pwd no es el nombre de un directorio.
  2. Sí: ls sin el argumento directorio lista los archivos y directorios en el directorio actual.
  3. Sí: utiliza la ruta absoluta explícitamente.

Sintaxis General de un Comando Shell


Ya hemos visto comandos, opciones y argumentos, pero quizás sea útil formalizar alguna terminología.

Considere el siguiente comando como un ejemplo general de un comando, que diseccionaremos en sus partes componentes:

BASH

$ ls -F /
Sintaxis general de un comando del shell

ls es el comando, con una opción -F y un argumento /. Ya hemos visto opciones que empiezan con un guión (-), conocidas como opciones cortas, o con dos guiones (--), conocidas como opciones largas. las [Opciones] cambian el comportamiento de un comando y los [Argumentos] le indican al comando sobre qué operar (por ejemplo, archivos y directorios). A veces, las opciones y los argumentos se denominan parámetros. Un comando puede ser llamado con más de una opción y más de un argumento, pero un comando no siempre requiere un argumento o una opción.

Puede que a veces veas que se hace referencia a las opciones como interruptores o banderas, especialmente para opciones que no tienen argumento. En esta lección utilizaremos el término opción.

Cada parte está separada por espacios. Si omite el espacio entre ls y -F el shell buscará un comando llamado ls-F, que no existe. Además, las mayúsculas pueden ser importantes. Por ejemplo, ls -s mostrará el tamaño de los ficheros y directorios junto a los nombres, mientras que ls -S ordenará los ficheros y directorios por tamaño, como se muestra a continuación:

BASH

$ cd ~/Desktop/shell-lesson-data
$ ls -s exercise-data

SALIDA

total 28
 4 animal-counts   4 creatures  12 numbers.txt   4 alkanes   4 writing

Tenga en cuenta que los tamaños devueltos por ls -s están en bloques. Como éstos se definen de forma diferente para los distintos sistemas operativos, es posible que no obtenga las mismas cifras que en el ejemplo.

BASH

$ ls -S exercise-data

SALIDA

animal-counts  creatures  alkanes  writing  numbers.txt

Juntando todo esto, nuestro comando ls -F / de arriba nos da un listado de ficheros y directorios en el directorio raíz /. A continuación se muestra un ejemplo de la salida que puede obtener del comando anterior:

BASH

$ ls -F /

SALIDA

Applications/         System/
Library/              Users/
Network/              Volumes/

Nelle’s Pipeline: Organización de ficheros

Sabiendo esto sobre archivos y directorios, Nelle está lista para organizar los archivos que la máquina de ensayo de proteínas creará.

Crea un directorio llamado north-pacific-gyre (para recordar de dónde proceden los datos), que contendrá los archivos de datos de la máquina de ensayo y sus scripts de procesamiento de datos.

Cada una de sus muestras físicas está etiquetada según la convención de su laboratorio con un ID único de diez caracteres, como “NENE01729A”. Este ID es el que utiliza en su registro de recogida para anotar la ubicación, la hora, la profundidad y otras características de la muestra, por lo que decide utilizarlo en el nombre de archivo de cada fichero de datos. Como la salida de la máquina de ensayo es texto sin formato, llamará a sus archivos NENE01729A.txt, NENE01812A.txt, etcétera. Los 1520 ficheros irán en el mismo directorio.

Ahora en su directorio actual shell-lesson-data, Nelle puede ver qué ficheros tiene usando el comando

BASH

$ ls north-pacific-gyre/

Este comando es mucho para escribir, pero puede dejar que el shell haga la mayor parte del trabajo a través de lo que se llama completar pestañas. Si escribe:

BASH

$ ls nor

y, a continuación, pulsa Tab (la tecla de tabulación de su teclado), el shell completa automáticamente el nombre del directorio por ella:

BASH

$ ls north-pacific-gyre/

Pulsar Tab de nuevo no hace nada, ya que hay múltiples posibilidades; pulsando Tab dos veces aparece una lista de todos los archivos.

Si Nelle pulsa G y después Tab de nuevo, el intérprete de comandos añadirá “goo”, ya que todos los archivos que empiezan por “g” comparten los tres primeros caracteres “goo”.

BASH

$ ls north-pacific-gyre/goo

Para ver todos esos archivos, puede pulsar Tab dos veces más.

BASH

ls north-pacific-gyre/goo
goodiff.sh   goostats.sh

Esto se llama completar pestañas, y lo veremos en muchas otras herramientas a medida que avancemos.

Puntos Clave

  • El sistema de ficheros se encarga de gestionar la información del disco.
  • La información se almacena en ficheros, que a su vez se almacenan en directorios (carpetas).
  • Los directorios también pueden almacenar otros directorios, que entonces forman un árbol de directorios.
  • pwd imprime el directorio de trabajo actual del usuario.
  • ls [path] imprime un listado de un fichero o directorio específico; ls por sí solo lista el directorio de trabajo actual.
  • cd [path] cambia el directorio de trabajo actual.
  • La mayoría de los comandos toman opciones que comienzan con -.
  • Los nombres de directorio en una ruta se separan con / en Unix, pero \ en Windows.
  • / por sí solo es el directorio raíz de todo el sistema de ficheros.
  • Una ruta absoluta especifica una ubicación desde la raíz del sistema de ficheros.
  • Una ruta relativa especifica una ubicación a partir de la ubicación actual.
  • . por sí solo significa ‘el directorio actual’; .. significa ‘el directorio por encima del actual’.

Content from Trabajar con archivos y directorios


Última actualización: 2025-04-03 | Mejora esta página

Hoja de ruta

Preguntas

  • ¿Cómo puedo crear, copiar y borrar archivos y directorios?
  • ¿Cómo puedo editar los archivos?

Objetivos

  • Eliminar, copiar y mover archivos y/o directorios especificados.
  • Crea archivos en esa jerarquía utilizando un editor o copiando y renombrando archivos existentes.
  • Crea una jerarquía de directorios que coincide con un diagrama dado.

Creando directorios


Ahora sabemos cómo explorar archivos y directorios, pero ¿cómo los creamos en primer lugar?

En este episodio aprenderemos a crear y mover archivos y directorios, usando el directorio exercise-data/writing como ejemplo.

Primer paso: ver dónde estamos y qué tenemos ya

Deberíamos seguir en el directorio shell-lesson-data del Escritorio, lo que podemos comprobar utilizando:

BASH

$ pwd

SALIDA

/Users/nelle/Desktop/shell-lesson-data

A continuación nos moveremos al directorio exercise-data/writing y veremos qué contiene:

BASH

$ cd exercise-data/writing/
$ ls -F

SALIDA

haiku.txt  LittleWomen.txt

Crear un directorio

Vamos a crear un nuevo directorio llamado thesis usando el comando mkdir thesis (que no tiene salida):

BASH

$ mkdir thesis

Como puede adivinar por su nombre, mkdir significa hacer directorio. Como thesis es una ruta relativa (es decir, no tiene una barra al principio, como /what/ever/thesis), el nuevo directorio se crea en el directorio de trabajo actual:

BASH

$ ls -F

SALIDA

haiku.txt  LittleWomen.txt  thesis/

Como acabamos de crear el directorio thesis, todavía no hay nada en él:

BASH

$ ls -F thesis

Tenga en cuenta que mkdir no se limita a crear directorios individuales de uno en uno. La opción -p permite a mkdir crear un directorio con subdirectorios anidados en una sola operación:

BASH

$ mkdir -p ../project/data ../project/results

La opción -R del comando ls listará todos los subdirectorios anidados dentro de un directorio. Usemos ls -FR para listar recursivamente la nueva jerarquía de directorios que acabamos de crear en el directorio project:

BASH

$ ls -FR ../project

SALIDA

../project/:
data/  results/

../project/data:

../project/results:

Dos formas de hacer lo mismo

Utilizar el shell para crear un directorio no es diferente de utilizar un explorador de archivos. Si abre el directorio actual utilizando el explorador gráfico de archivos de su sistema operativo, el directorio thesis también aparecerá allí. Aunque el shell y el explorador de archivos son dos formas diferentes de interactuar con los archivos, los archivos y directorios en sí son los mismos.

Buenos nombres para archivos y directorios

Los nombres complicados de archivos y directorios pueden hacerte la vida imposible cuando trabajas en la línea de comandos. Aquí te ofrecemos algunos consejos útiles para los nombres de tus archivos y directorios.

  1. No uses espacios.

Los espacios pueden hacer que un nombre tenga más sentido, pero dado que los espacios se utilizan para separar argumentos en la línea de órdenes, es mejor evitarlos en los nombres de ficheros y directorios. Puede utilizar - o _ en su lugar (por ejemplo, north-pacific-gyre/ en lugar de north pacific gyre/). Para comprobarlo, pruebe a escribir mkdir north pacific gyre y vea qué directorio (¡o directorios!) se crean al comprobarlo con ls -F.

  1. No empiece el nombre con - (guión).

Los comandos tratan los nombres que empiezan por - como opciones.

  1. Utiliza letras, números, . (punto o ‘punto final’), - (guión) y _ (guión bajo).

Muchos otros caracteres tienen significados especiales en la línea de comandos. Aprenderemos sobre algunos de ellos durante esta lección. Hay caracteres especiales que pueden hacer que su comando no funcione como se espera e incluso pueden provocar la pérdida de datos.

Si necesita referirse a nombres de archivos o directorios que tienen espacios u otros caracteres especiales, debe rodear el nombre entre [comillas] simples (https://www.gnu.org/software/bash/manual/html_node/Quoting.html) ('').

Crear un archivo de texto

Cambiemos nuestro directorio de trabajo a thesis usando cd, luego ejecute un editor de texto llamado Nano para crear un archivo llamado draft.txt:

BASH

$ cd thesis
$ nano draft.txt

¿Qué Editor?

Cuando decimos “nano es un editor de texto” nos referimos realmente a “texto”. Sólo puede trabajar con datos de caracteres planos, no con tablas, imágenes o cualquier otro medio amigable. Lo utilizamos en los ejemplos porque es uno de los editores de texto menos complejos. Sin embargo, debido a esta característica, puede que no sea lo suficientemente potente o flexible para el trabajo que necesites hacer después de este taller. En sistemas Unix (como Linux y macOS), muchos programadores utilizan Emacs o Vim (ambos requieren más tiempo de aprendizaje), o un editor gráfico como Gedit o VScode. En Windows, puede utilizar Notepad++. Windows también tiene un editor incorporado llamado notepad que se puede ejecutar desde la línea de comandos de la misma manera que nano para los propósitos de esta lección.

Independientemente del editor que utilices, necesitarás saber dónde busca y guarda los archivos. Si lo inicia desde el intérprete de comandos, (probablemente) utilizará su directorio de trabajo actual como ubicación predeterminada. Si utilizas el menú de inicio de tu ordenador, puede que quiera guardar los archivos en tu Escritorio o en el directorio Documentos. Puede cambiar esto navegando a otro directorio la primera vez que ‘Guarde como…’

Escribamos unas líneas de texto.

captura de pantalla del editor de texto nano en acción con el texto Ya no es publicar o perecer, es compartir y prosperar

Una vez que estemos satisfechos con nuestro texto, podemos pulsar Ctrl+O (pulsar la tecla Ctrl o Control y, sin soltarla, pulsar la tecla O) para escribir nuestros datos en el disco. Se nos pedirá que proporcionemos un nombre para el archivo que contendrá nuestro texto. Pulse Return para aceptar el valor predeterminado sugerido de draft.txt.

Una vez guardado nuestro archivo, podemos usar Ctrl+X para salir del editor y volver al shell.

Tecla Control, Ctrl o ^

La tecla Control también se llama tecla ‘Ctrl’. Hay varias formas de describir el uso de la tecla Control. Por ejemplo, puede ver una instrucción para pulsar la tecla Control y, mientras la mantiene pulsada, pulsar la tecla X, descrita como cualquiera de:

  • Control-X
  • Control+X
  • Ctrl-X
  • Ctrl+X
  • ^X
  • C-x

En nano, en la parte inferior de la pantalla verás ^G Get Help ^O WriteOut. Esto significa que puedes usar Control-G para obtener ayuda y Control-O para guardar tu archivo.

nano no deja ninguna salida en la pantalla después de salir, pero ls muestra ahora que hemos creado un fichero llamado draft.txt:

BASH

$ ls

SALIDA

draft.txt

Crear archivos de otra manera

Hemos visto cómo crear archivos de texto utilizando el editor nano. Ahora, prueba el siguiente comando:

BASH

$ touch my_file.txt
  1. ¿Qué hizo el comando touch? Cuando miras tu directorio actual usando el explorador de archivos GUI, ¿aparece el archivo?

  2. Utilice ls -l para inspeccionar los archivos. ¿Qué tamaño tiene my_file.txt?

  3. ¿Cuándo podría querer crear un archivo de esta manera?

  1. El comando touch genera un nuevo fichero llamado my_file.txt en tu directorio actual. Puede observar este archivo recién generado escribiendo ls en la línea de comandos. también puede ver my_file.txt en su explorador de archivos GUI.

  2. Al inspeccionar el fichero con ls -l, observe que el tamaño de my_file.txt es de 0 bytes. En otras palabras, no contiene datos. Si abre my_file.txt con su editor de texto, aparecerá en blanco.

  3. Algunos programas no generan archivos de salida por sí mismos, sino que requieren que ya se hayan generado archivos vacíos. Cuando el programa se ejecuta, busca un archivo existente para rellenarlo con su salida. El comando touch le permite generar eficientemente un archivo de texto en blanco para ser utilizado por tales programas.

Crear archivos de otra manera (continued)

Para evitar confusiones más adelante, le sugerimos que elimine el fichero que acaba de crear antes de continuar con el resto del episodio, de lo contrario los resultados futuros pueden variar de los dados en la lección. Para ello, utilice el siguiente comando:

BASH

$ rm my_file.txt

¿Qué hay en un nombre?

Te habrás dado cuenta de que todos los archivos de Nelle se llaman ‘algo punto algo’, y en esta parte de la lección, siempre hemos utilizado la extensión .txt. Esto es sólo una convención; podemos llamar a un archivo mythesis o casi cualquier otra cosa que queramos. Sin embargo, la mayoría de la gente utiliza nombres de dos partes la mayor parte del tiempo para ayudarles (y a sus programas) a distinguir diferentes tipos de ficheros. La segunda parte de un nombre de este tipo se llama extensión de nombre de fichero e indica qué tipo de datos contiene el fichero: .txt señala un fichero de texto plano, .pdf indica un documento PDF, .cfg es un fichero de configuración lleno de parámetros para algún programa, .png es una imagen PNG, etc.

Esto es sólo una convención, aunque importante. Los archivos sólo contienen bytes; depende de nosotros y de nuestros programas interpretar esos bytes de acuerdo con las reglas para archivos de texto plano, documentos PDF, archivos de configuración, imágenes, etcétera.

Nombrar una imagen PNG de una ballena como whale.mp3 no la convierte mágicamente en una grabación del canto de una ballena, aunque puede hacer que el sistema operativo asocie el fichero con un programa reproductor de música. En este caso, si alguien hace doble clic en whale.mp3 en un programa explorador de archivos, el reproductor de música intentará automáticamente (y por error) abrir el archivo whale.mp3.

Moviendo ficheros y directorios


Volviendo al directorio shell-lesson-data/exercise-data/writing,

BASH

$ cd ~/Desktop/shell-lesson-data/exercise-data/writing

En nuestro directorio thesis tenemos un fichero draft.txt que no es un nombre particularmente informativo, así que cambiemos el nombre del fichero usando mv, que es la abreviatura de ‘move’:

BASH

$ mv thesis/draft.txt thesis/quotes.txt

El primer argumento dice mv lo que estamos ‘moviendo’, mientras que el segundo es a dónde va a ir. En este caso, estamos moviendo thesis/draft.txt a thesis/quotes.txt, lo que tiene el mismo efecto que renombrar el fichero. Efectivamente, ls nos muestra que thesis contiene ahora un fichero llamado quotes.txt:

BASH

$ ls thesis

SALIDA

quotes.txt

Hay que tener cuidado al especificar el nombre del fichero de destino, ya que mv sobrescribirá silenciosamente cualquier fichero existente con el mismo nombre, lo que podría provocar la pérdida de datos. Por defecto, mv no pedirá confirmación antes de sobrescribir archivos. Sin embargo, una opción adicional, mv -i (o mv --interactive), hará que mv solicite dicha confirmación.

Tenga en cuenta que mv también funciona en directorios.

Movamos quotes.txt al directorio de trabajo actual. Usaremos mv una vez más, pero esta vez usaremos sólo el nombre de un directorio como segundo argumento para decirle a mv que queremos mantener el nombre del fichero pero ponerlo en un lugar nuevo. (En este caso, el nombre de directorio que utilizamos es el nombre de directorio especial . que hemos mencionado antes.

BASH

$ mv thesis/quotes.txt .

El efecto es mover el fichero del directorio en el que estaba al directorio de trabajo actual. ls nos muestra ahora que thesis está vacío:

BASH

$ ls thesis

SALIDA

$

Alternativamente, podemos confirmar que el fichero quotes.txt ya no está presente en el directorio thesis intentando listarlo explícitamente:

BASH

$ ls thesis/quotes.txt

ERROR

ls: cannot access 'thesis/quotes.txt': No such file or directory

ls con un nombre de fichero o directorio como argumento sólo muestra el fichero o directorio solicitado. Si el fichero dado como argumento no existe, el shell devuelve un error como vimos anteriormente. Podemos usar esto para ver que quotes.txt está ahora presente en nuestro directorio actual:

BASH

$ ls quotes.txt

SALIDA

quotes.txt

Moviendo archivos a una nueva carpeta

Después de ejecutar los siguientes comandos, Jamie se da cuenta de que puso los archivos sucrose.dat y maltose.dat en la carpeta equivocada. Los ficheros deberían haberse colocado en la carpeta raw.

BASH

$ ls -F
 analyzed/ raw/
$ ls -F analyzed
fructose.dat glucose.dat maltose.dat sucrose.dat
$ cd analyzed

Rellene los espacios en blanco para mover estos archivos a la carpeta raw/ (es decir, aquella en la que olvidó ponerlos)

BASH

$ mv sucrose.dat maltose.dat ____/____

BASH

$ mv sucrose.dat maltose.dat ../raw

Recuerde que .. se refiere al directorio padre (es decir, uno por encima del directorio actual) y que . se refiere al directorio actual.

Copiando ficheros y directorios


El comando cp funciona de forma muy parecida a mv, excepto que copia un fichero en lugar de moverlo. Podemos comprobar que ha hecho lo correcto utilizando ls con dos rutas como argumentos — como la mayoría de los comandos Unix, ls puede recibir múltiples rutas a la vez:

BASH

$ cp quotes.txt thesis/quotations.txt
$ ls quotes.txt thesis/quotations.txt

SALIDA

quotes.txt   thesis/quotations.txt

También podemos copiar un directorio y todo su contenido utilizando la opción recursive -r, por ejemplo, para hacer una copia de seguridad de un directorio:

BASH

$ cp -r thesis thesis_backup

Podemos comprobar el resultado listando el contenido de los directorios thesis y thesis_backup:

BASH

$ ls thesis thesis_backup

SALIDA

thesis:
quotations.txt

thesis_backup:
quotations.txt

Es importante incluir la bandera -r. Si quieres copiar un directorio y omites esta opción verás un mensaje de que el directorio ha sido omitido porque -r not specified.

BASH

$ cp thesis thesis_backup
cp: -r not specified; omitting directory 'thesis'

Renombrar Archivos

Supongamos que ha creado un archivo de texto plano en su directorio actual para contener una lista de las pruebas estadísticas que necesitará realizar para analizar sus datos, y lo ha llamado statstics.txt

Después de crear y guardar este fichero te das cuenta de que has escrito mal el nombre Quieres corregir el error, ¿cuál de los siguientes comandos podrías utilizar para hacerlo?

  1. cp statstics.txt statistics.txt
  2. mv statstics.txt statistics.txt
  3. mv statstics.txt .
  4. cp statstics.txt .
  1. No. Aunque esto crearía un fichero con el nombre correcto, el fichero con el nombre incorrecto todavía existe en el directorio y tendría que ser borrado.
  2. Sí, esto funcionaría para cambiar el nombre del archivo.
  3. No, el punto(.) indica dónde mover el archivo, pero no proporciona un nuevo nombre de archivo; no se pueden crear nombres de archivo idénticos.
  4. No, el punto(.) indica dónde copiar el archivo, pero no proporciona un nuevo nombre de archivo; no se pueden crear nombres de archivo idénticos.

Mover y copiar

¿Cuál es la salida del comando de cierre ls en la secuencia que se muestra a continuación?

BASH

$ pwd

SALIDA

/Users/jamie/data

BASH

$ ls

SALIDA

proteins.dat

BASH

$ mkdir recombined
$ mv proteins.dat recombined/
$ cp recombined/proteins.dat ../proteins-saved.dat
$ ls
  1. proteins-saved.dat recombined
  2. recombined
  3. proteins.dat recombined
  4. proteins-saved.dat

Empezamos en el directorio /Users/jamie/data, y creamos una nueva carpeta llamada recombined. La segunda línea mueve (mv) el fichero proteins.dat a la nueva carpeta (recombined). La tercera línea hace una copia del fichero que acabamos de mover. La parte complicada aquí es dónde se ha copiado el archivo. Recuerda que .. significa ‘subir un nivel’, así que el fichero copiado está ahora en /Users/jamie. Tenga en cuenta que .. se interpreta con respecto al directorio de trabajo actual, no con respecto a la ubicación del fichero que se está copiando. Así, lo único que se mostrará usando ls (en /Users/jamie/data) es la carpeta recombinada.

  1. No, véase la explicación anterior. proteins-saved.dat se encuentra en /Users/jamie
  2. No, véase la explicación anterior. proteins.dat se encuentra en /Users/jamie/data/recombined
  3. No, véase la explicación anterior. proteins-saved.dat se encuentra en /Users/jamie

Eliminando ficheros y directorios


Volviendo al directorio shell-lesson-data/exercise-data/writing, vamos a ordenar este directorio eliminando el archivo quotes.txt que hemos creado. El comando Unix que utilizaremos para ello es rm (abreviatura de ‘remove’):

BASH

$ rm quotes.txt

Podemos confirmar que el archivo se ha ido usando ls:

BASH

$ ls quotes.txt

ERROR

ls: cannot access 'quotes.txt': No such file or directory

Borrar es para siempre

El shell de Unix no tiene una papelera de la que podamos recuperar los ficheros borrados (aunque la mayoría de las interfaces gráficas de Unix sí la tienen). En su lugar, cuando borramos archivos, éstos son desvinculados del sistema de archivos para que su espacio de almacenamiento en disco pueda ser reciclado. Existen herramientas para encontrar y recuperar archivos borrados, pero no hay garantía de que funcionen en una situación concreta, ya que el ordenador puede reciclar el espacio del archivo en el disco inmediatamente.

Usando rm con seguridad

¿Qué ocurre cuando ejecutamos rm -i thesis_backup/quotations.txt? ¿Por qué querríamos esta protección cuando usamos rm?

SALIDA

rm: remove regular file 'thesis_backup/quotations.txt'? y

La opción -i preguntará antes (de cada) borrado (use Y para confirmar el borrado o N para mantener el fichero). El shell Unix no tiene papelera, por lo que todos los archivos eliminados desaparecerán para siempre. Utilizando la opción -i, tenemos la posibilidad de comprobar que estamos borrando sólo los ficheros que queremos eliminar.

Si intentamos eliminar el directorio thesis utilizando rm thesis, obtenemos un mensaje de error:

BASH

$ rm thesis

ERROR

rm: cannot remove 'thesis': Is a directory

Esto ocurre porque rm por defecto sólo funciona en ficheros, no en directorios.

rm puede eliminar un directorio y todo su contenido si usamos la opción recursiva -r, y lo hará sin ninguna petición de confirmación:

BASH

$ rm -r thesis

Dado que no hay forma de recuperar ficheros borrados usando el shell, rm -r debe usarse con mucha precaución (puedes considerar añadir la opción interactiva rm -r -i).

Operaciones con múltiples archivos y directorios


A menudo es necesario copiar o mover varios ficheros a la vez. Esto puede hacerse proporcionando una lista de nombres de ficheros individuales, o especificando un patrón de nombres utilizando comodines. Los comodines son caracteres especiales que se pueden utilizar para representar caracteres desconocidos o conjuntos de caracteres al navegar por el sistema de ficheros Unix.

Copia con varios nombres de archivo

Para este ejercicio, puede probar los comandos en el directorio shell-lesson-data/exercise-data.

En el siguiente ejemplo, ¿qué hace cp cuando se le dan varios nombres de archivo y un nombre de directorio?

BASH

$ mkdir backup
$ cp creatures/minotaur.dat creatures/unicorn.dat backup/

En el ejemplo siguiente, ¿qué hace cp cuando se le dan tres o más nombres de archivo?

BASH

$ cd creatures
$ ls -F

SALIDA

basilisk.dat  minotaur.dat  unicorn.dat

BASH

$ cp minotaur.dat unicorn.dat basilisk.dat

Si se da más de un nombre de fichero seguido de un nombre de directorio (es decir, el directorio de destino debe ser el último argumento), cp copia los ficheros en el directorio nombrado.

Si se le dan tres nombres de fichero, cp lanza un error como el siguiente, porque está esperando un nombre de directorio como último argumento.

ERROR

cp: target 'basilisk.dat' is not a directory

Uso de comodines para acceder a varios archivos a la vez

Comodines

* es un código comodín, que representa otros cero o más caracteres. Consideremos el directorio shell-lesson-data/exercise-data/alkanes: *.pdb representa ethane.pdb, propane.pdb y todos los archivos que terminan en ‘.pdb’. En cambio, p*.pdb sólo representa a pentane.pdb y propane.pdb, porque la p de delante sólo puede representar nombres de fichero que empiecen por la letra p.

? también es un comodín, pero representa exactamente un carácter. Así, ?ethane.pdb podría representar methane.pdb mientras que *ethane.pdb representa tanto ethane.pdb como methane.pdb.

Los comodines se pueden utilizar combinados entre sí. Por ejemplo, ???ane.pdb indica tres caracteres seguidos de ane.pdb, dando cubane.pdb ethane.pdb octane.pdb.

Cuando el shell ve un comodín, lo expande para crear una lista de nombres de archivo coincidentes antes de ejecutar el comando precedente. Como excepción, si una expresión comodín no coincide con ningún archivo, Bash pasará la expresión como argumento al comando tal cual. Por ejemplo, teclear ls *.pdf en el directorio alkanes (que sólo contiene ficheros cuyos nombres terminan en .pdb) da como resultado un mensaje de error que indica que no existe ningún fichero llamado *.pdf. Sin embargo, generalmente comandos como wc y ls ven las listas de nombres de ficheros que coinciden con estas expresiones, pero no los comodines en sí. Es el shell, no los otros programas, el que expande los comodines.

Listar los nombres de archivos que coinciden con un patrón

Cuando se ejecuta en el directorio alkanes, ¿qué comando(s) ls producirá(n) este resultado?

ethane.pdb methane.pdb

  1. ls *t*ane.pdb
  2. ls *t?ne.*
  3. ls *t??ne.pdb
  4. ls ethane.*

La solución es 3.

1. muestra todos los ficheros cuyos nombres contienen cero o más caracteres (*) seguidos de la letra t, luego cero o más caracteres (*) seguidos de ane.pdb. Esto da ethane.pdb methane.pdb octane.pdb pentane.pdb.

2. muestra todos los ficheros cuyos nombres empiezan por cero o más caracteres (*) seguidos de la letra t, luego un solo carácter (?), luego ne. seguido de cero o más caracteres (*). Esto nos dará octane.pdb y pentane.pdb pero no coincide con nada que termine en thane.pdb.

3. soluciona los problemas de la opción 2 haciendo coincidir dos caracteres (??) entre t y ne. Esta es la solución.

4. sólo muestra los archivos que empiezan por ethane..

Más sobre comodines

Sam tiene un directorio que contiene datos de calibración, conjuntos de datos y descripciones de los conjuntos de datos:

BASH

.
├── 2015-10-23-calibration.txt
├── 2015-10-23-dataset1.txt
├── 2015-10-23-dataset2.txt
├── 2015-10-23-dataset_overview.txt
├── 2015-10-26-calibration.txt
├── 2015-10-26-dataset1.txt
├── 2015-10-26-dataset2.txt
├── 2015-10-26-dataset_overview.txt
├── 2015-11-23-calibration.txt
├── 2015-11-23-dataset1.txt
├── 2015-11-23-dataset2.txt
├── 2015-11-23-dataset_overview.txt
├── backup
│   ├── calibration
│   └── datasets
└── send_to_bob
    ├── all_datasets_created_on_a_23rd
    └── all_november_files

Antes de salir a otra excursión, quiere hacer una copia de seguridad de sus datos y enviar algunos conjuntos de datos a su colega Bob. Sam utiliza los siguientes comandos para realizar el trabajo:

BASH

$ cp *dataset* backup/datasets
$ cp ____calibration____ backup/calibration
$ cp 2015-____-____ send_to_bob/all_november_files/
$ cp ____ send_to_bob/all_datasets_created_on_a_23rd/

Ayuda a Sam rellenando los espacios en blanco.

La estructura de directorios resultante debería ser la siguiente

BASH

.
├── 2015-10-23-calibration.txt
├── 2015-10-23-dataset1.txt
├── 2015-10-23-dataset2.txt
├── 2015-10-23-dataset_overview.txt
├── 2015-10-26-calibration.txt
├── 2015-10-26-dataset1.txt
├── 2015-10-26-dataset2.txt
├── 2015-10-26-dataset_overview.txt
├── 2015-11-23-calibration.txt
├── 2015-11-23-dataset1.txt
├── 2015-11-23-dataset2.txt
├── 2015-11-23-dataset_overview.txt
├── backup
│   ├── calibration
│   │   ├── 2015-10-23-calibration.txt
│   │   ├── 2015-10-26-calibration.txt
│   │   └── 2015-11-23-calibration.txt
│   └── datasets
│       ├── 2015-10-23-dataset1.txt
│       ├── 2015-10-23-dataset2.txt
│       ├── 2015-10-23-dataset_overview.txt
│       ├── 2015-10-26-dataset1.txt
│       ├── 2015-10-26-dataset2.txt
│       ├── 2015-10-26-dataset_overview.txt
│       ├── 2015-11-23-dataset1.txt
│       ├── 2015-11-23-dataset2.txt
│       └── 2015-11-23-dataset_overview.txt
└── send_to_bob
    ├── all_datasets_created_on_a_23rd
    │   ├── 2015-10-23-dataset1.txt
    │   ├── 2015-10-23-dataset2.txt
    │   ├── 2015-10-23-dataset_overview.txt
    │   ├── 2015-11-23-dataset1.txt
    │   ├── 2015-11-23-dataset2.txt
    │   └── 2015-11-23-dataset_overview.txt
    └── all_november_files
        ├── 2015-11-23-calibration.txt
        ├── 2015-11-23-dataset1.txt
        ├── 2015-11-23-dataset2.txt
        └── 2015-11-23-dataset_overview.txt

BASH

$ cp *calibration.txt backup/calibration
$ cp 2015-11-* send_to_bob/all_november_files/
$ cp *-23-dataset* send_to_bob/all_datasets_created_on_a_23rd/

Organizar directorios y archivos

Jamie está trabajando en un proyecto, y ve que sus archivos no están muy bien organizados:

BASH

$ ls -F

SALIDA

analyzed/  fructose.dat    raw/   sucrose.dat

Los archivos fructose.dat y sucrose.dat contienen los resultados de su análisis de datos. ¿Qué comando(s) cubierto(s) en esta lección necesita ejecutar para que los comandos de abajo produzcan la salida mostrada?

BASH

$ ls -F

SALIDA

analyzed/   raw/

BASH

$ ls analyzed

SALIDA

fructose.dat    sucrose.dat

BASH

mv *.dat analyzed

Jamie necesita mover sus ficheros fructose.dat y sucrose.dat al directorio analyzed. El intérprete de comandos expandirá *.dat para que coincida con todos los archivos .dat del directorio actual. A continuación, el comando mv mueve la lista de archivos .dat al directorio analizado.

Reproducir una estructura de carpetas

Está comenzando un nuevo experimento y le gustaría duplicar la estructura de directorios de su experimento anterior para poder añadir nuevos datos.

Supongamos que el experimento anterior está en una carpeta llamada 2016-05-18, que contiene una carpeta data que a su vez contiene carpetas llamadas raw y processed que contienen ficheros de datos. El objetivo es copiar la estructura de carpetas de la carpeta 2016-05-18 en una carpeta llamada 2016-05-20 de modo que la estructura final de directorios tenga este aspecto:

SALIDA

2016-05-20/
└── data
   ├── processed
   └── raw

¿Cuál de la siguiente serie de comandos lograría este objetivo? ¿Qué harían los otros comandos?

BASH

$ mkdir 2016-05-20
$ mkdir 2016-05-20/data
$ mkdir 2016-05-20/data/processed
$ mkdir 2016-05-20/data/raw

BASH

$ mkdir 2016-05-20
$ cd 2016-05-20
$ mkdir data
$ cd data
$ mkdir raw processed

BASH

$ mkdir 2016-05-20/data/raw
$ mkdir 2016-05-20/data/processed

BASH

$ mkdir -p 2016-05-20/data/raw
$ mkdir -p 2016-05-20/data/processed

BASH

$ mkdir 2016-05-20
$ cd 2016-05-20
$ mkdir data
$ mkdir raw processed

Los dos primeros conjuntos de comandos logran este objetivo. El primer conjunto utiliza rutas relativas para crear el directorio de nivel superior antes que los subdirectorios.

El tercer conjunto de comandos dará un error porque el comportamiento por defecto de mkdir no creará un subdirectorio de un directorio inexistente: las carpetas de nivel intermedio deben crearse primero.

El cuarto conjunto de comandos logra este objetivo. Recuerde que la opción -p, seguida de una ruta de uno o más directorios, hará que mkdir cree los subdirectorios intermedios que sean necesarios.

El último conjunto de comandos genera los directorios ‘raw’ y ‘processed’ al mismo nivel que el directorio ‘data’.

Puntos Clave

  • cp [old] [new] copia un archivo.
  • mkdir [path] crea un nuevo directorio.
  • mv [old] [new] mueve (renombra) un archivo o directorio.
  • rm [path] elimina (borra) un archivo.
  • * coincide con cero o más caracteres en un nombre de archivo, por lo que *.txt coincide con todos los archivos que terminan en .txt.
  • ? coincide con cualquier carácter de un nombre de archivo, por lo que ?.txt coincide con a.txt pero no con any.txt.
  • El uso de la tecla Control puede describirse de muchas maneras, incluyendo Ctrl-X, Control-X y ^X.
  • El intérprete de comandos no tiene papelera: una vez que se borra algo, desaparece de verdad.
  • Los nombres de la mayoría de los ficheros son something.extension. La extensión no es necesaria y no garantiza nada, pero normalmente se utiliza para indicar el tipo de datos del archivo.
  • Dependiendo del tipo de trabajo que realice, es posible que necesite un editor de texto más potente que Nano.

Content from Pipes y filtros


Última actualización: 2025-07-01 | Mejora esta página

Hoja de ruta

Preguntas

  • ¿Cómo puedo combinar comandos existentes para producir una salida deseada?
  • ¿Cómo puedo mostrar sólo una parte de la salida?

Objetivos

  • Explica la ventaja de enlazar comandos con pipes y filtros.
  • Combina secuencias de comandos para obtener una nueva salida
  • Redirige la salida de un comando a un archivo.
  • Explica qué ocurre normalmente si un programa o una pipe no recibe ninguna entrada para procesar.

Ahora que ya conocemos algunos comandos básicos, por fin podemos echar un vistazo a la característica más poderosa del shell: la facilidad con la que nos permite combinar programas existentes de nuevas formas. Empezaremos con el directorio shell-lesson-data/exercise-data/alkanes que contiene seis archivos que describen algunas moléculas orgánicas simples. La extensión .pdb indica que estos archivos están en formato Protein Data Bank, un formato de texto simple que especifica el tipo y la posición de cada átomo en la molécula.

BASH

$ ls

SALIDA

cubane.pdb    methane.pdb    pentane.pdb
ethane.pdb    octane.pdb     propane.pdb

Vamos a ejecutar un comando de ejemplo:

BASH

$ wc cubane.pdb

SALIDA

20  156 1158 cubane.pdb

wc es el comando ‘word count’: cuenta el número de líneas, palabras y caracteres en ficheros (devolviendo los valores en ese orden de izquierda a derecha).

Si ejecutamos el comando wc *.pdb, el * en *.pdb coincide con cero o más caracteres, por lo que el shell convierte *.pdb en una lista de todos los ficheros .pdb en el directorio actual:

BASH

$ wc *.pdb

SALIDA

  20  156  1158  cubane.pdb
  12  84   622   ethane.pdb
   9  57   422   methane.pdb
  30  246  1828  octane.pdb
  21  165  1226  pentane.pdb
  15  111  825   propane.pdb
 107  819  6081  total

Observe que wc *.pdb también muestra el número total de todas las líneas en la última línea de la salida.

Si ejecutamos wc -l en lugar de sólo wc, la salida muestra sólo el número de líneas por fichero:

BASH

$ wc -l *.pdb

SALIDA

  20  cubane.pdb
  12  ethane.pdb
   9  methane.pdb
  30  octane.pdb
  21  pentane.pdb
  15  propane.pdb
 107  total

Las opciones -m y -w también pueden usarse con el comando wc para mostrar sólo el número de caracteres o el número de palabras, respectivamente.

¿Por qué no hace nada?

¿Qué pasa si un comando debe procesar un fichero, pero no le damos un nombre de fichero? Por ejemplo, ¿qué pasa si escribimos:

BASH

$ wc -l

pero no escribe *.pdb (ni nada más) después del comando? Dado que no tiene ningún nombre de archivo, wc asume que se supone que debe procesar la entrada dada en el símbolo del sistema, por lo que simplemente se sienta allí y espera a que le demos algunos datos de forma interactiva. Desde el exterior, sin embargo, todo lo que vemos es que se sienta allí, y el comando no parece hacer nada.

Si comete este tipo de error, puede salir de este estado manteniendo pulsada la tecla control (Ctrl) y pulsando la letra C una vez: Ctrl+C. A continuación, suelta ambas teclas.

Capturando la salida de los comandos


¿Cuál de estos ficheros contiene menos líneas? Es una pregunta fácil de responder cuando sólo hay seis ficheros, pero ¿y si hubiera 6000? Nuestro primer paso hacia la solución es ejecutar el comando

BASH

$ wc -l *.pdb > lengths.txt

El símbolo mayor que, >, indica al shell que redirija la salida del comando a un fichero en lugar de imprimirla en pantalla. Este comando no imprime ninguna salida en pantalla, porque todo lo que wc hubiera impreso ha ido al fichero lengths.txt en su lugar. Si el archivo no existe antes de emitir el comando, el shell creará el archivo. Si el archivo ya existe, se sobrescribirá silenciosamente, lo que puede provocar la pérdida de datos. Por lo tanto, los comandos redirigir requieren precaución.

ls lengths.txt confirma que el archivo existe:

BASH

$ ls lengths.txt

SALIDA

lengths.txt

Ahora podemos enviar el contenido de lengths.txt a la pantalla usando cat lengths.txt. El comando cat recibe su nombre de ‘concatenar’, es decir, unir, e imprime el contenido de los ficheros uno tras otro. En este caso sólo hay un fichero, así que cat sólo nos muestra lo que contiene:

BASH

$ cat lengths.txt

SALIDA

  20  cubane.pdb
  12  ethane.pdb
   9  methane.pdb
  30  octane.pdb
  21  pentane.pdb
  15  propane.pdb
 107  total

Salida Página por Página

Continuaremos usando cat en esta lección, por conveniencia y consistencia, pero tiene la desventaja de que siempre vuelca todo el fichero en la pantalla. Más útil en la práctica es el comando less (por ejemplo less lengths.txt). Esto muestra un pantallazo del archivo, y luego se detiene. Puedes avanzar una pantalla pulsando la barra espaciadora, o retroceder una pulsando b. Pulsa q para salir.

Filtrar salida


A continuación usaremos el comando sort para ordenar el contenido del archivo lengths.txt. Pero primero haremos un ejercicio para aprender un poco sobre el comando sort:

¿Qué hace sort -n?

El fichero shell-lesson-data/exercise-data/numbers.txt contiene las siguientes líneas:

10
2
19
22
6

Si ejecutamos sort en este fichero, la salida es:

SALIDA

10
19
2
22
6

Si ejecutamos sort -n en el mismo fichero, obtenemos esto en su lugar:

SALIDA

2
6
10
19
22

Explica por qué -n tiene este efecto.

La opción -n especifica una ordenación numérica en lugar de alfanumérica.

También usaremos la opción -n para especificar que la ordenación sea numérica en lugar de alfanumérica. Esto no cambia el fichero, sino que envía el resultado ordenado a la pantalla:

BASH

$ sort -n lengths.txt

SALIDA

  9  methane.pdb
 12  ethane.pdb
 15  propane.pdb
 20  cubane.pdb
 21  pentane.pdb
 30  octane.pdb
107  total

Podemos poner la lista ordenada de líneas en otro fichero temporal llamado sorted-lengths.txt poniendo > sorted-lengths.txt después del comando, igual que usamos > lengths.txt para poner la salida de wc en lengths.txt. Una vez hecho esto, podemos ejecutar otro comando llamado head para obtener las primeras líneas en sorted-lengths.txt:

BASH

$ sort -n lengths.txt > sorted-lengths.txt
$ head -n 1 sorted-lengths.txt

SALIDA

  9  methane.pdb

Usar -n 1 con head le dice que sólo queremos la primera línea del fichero; -n 20 obtendría las 20 primeras, y así sucesivamente. Como sorted-lengths.txt contiene las longitudes de nuestros ficheros ordenadas de menor a mayor, la salida de head debe ser el fichero con menos líneas.

Redireccionando al mismo fichero

Es una muy mala idea intentar redirigir la salida de un comando que opera sobre un fichero al mismo fichero. Por ejemplo:

BASH

$ sort -n lengths.txt > lengths.txt

Hacer algo como esto puede darte resultados incorrectos y/o borrar el contenido de lengths.txt.

¿Qué significa >>?

Hemos visto el uso de >, pero existe un operador similar >> que funciona de forma ligeramente diferente. Aprenderemos las diferencias entre estos dos operadores imprimiendo algunas cadenas. Podemos utilizar el comando echo para imprimir cadenas, por ejemplo

BASH

$ echo The echo command prints text

SALIDA

The echo command prints text

Ahora prueba los comandos de abajo para revelar la diferencia entre los dos operadores:

BASH

$ echo hello > testfile01.txt

y:

BASH

$ echo hello >> testfile02.txt

Sugerencia: Intenta ejecutar cada comando dos veces seguidas y luego examina los archivos de salida.

En el primer ejemplo con >, la cadena ‘hola’ se escribe en testfile01.txt, pero el fichero se sobreescribe cada vez que ejecutamos el comando.

Vemos en el segundo ejemplo que el operador >> también escribe ‘hola’ en un fichero (en este caso testfile02.txt), pero añade la cadena al fichero si ya existe (es decir, cuando lo ejecutamos por segunda vez).

Añadiendo datos

Ya hemos conocido el comando head, que imprime líneas desde el principio de un fichero. el comando tail es similar, pero imprime líneas desde el final del fichero.

Considera el fichero shell-lesson-data/exercise-data/animal-counts/animals.csv. Después de estos comandos, seleccione la respuesta que corresponde al fichero animals-subset.csv:

BASH

$ head -n 3 animals.csv > animals-subset.csv
$ tail -n 2 animals.csv >> animals-subset.csv
  1. Las tres primeras líneas de animals.csv
  2. Las dos últimas líneas de animals.csv son
  3. Las tres primeras líneas y las dos últimas líneas de animals.csv
  4. La segunda y la tercera línea de animals.csv

La opción 3 es correcta. Para que la opción 1 sea correcta sólo ejecutaríamos el comando head. Para que la opción 2 sea correcta sólo ejecutaríamos el comando tail. Para que la opción 4 sea correcta tendríamos que pasar la salida de head a tail -n 2 haciendo head -n 3 animals.csv | tail -n 2 > animals-subset.csv

Pasando la salida a otro comando


En nuestro ejemplo de encontrar el fichero con menos líneas, estamos usando dos ficheros intermedios lengths.txt y sorted-lengths.txt para almacenar la salida. Esta es una forma confusa de trabajar porque incluso una vez que entiendes lo que hacen wc, sort, y head, esos ficheros intermedios hacen difícil seguir lo que está pasando. Podemos hacerlo más fácil de entender ejecutando sort y head juntos:

BASH

$ sort -n lengths.txt | head -n 1

SALIDA

  9  methane.pdb

La barra vertical, |, entre los dos comandos se llama pipe. Le dice al shell que queremos usar la salida del comando de la izquierda como entrada para el comando de la derecha.

Esto ha eliminado la necesidad del archivo sorted-lengths.txt.

Combinando múltiples comandos


Nada nos impide encadenar pipes consecutivamente. Podemos, por ejemplo, enviar la salida de wc directamente a sort, y luego enviar la salida resultante a head. Esto elimina la necesidad de ficheros intermedios.

Empezaremos usando una pipe para enviar la salida de wc a sort:

BASH

$ wc -l *.pdb | sort -n

SALIDA

   9 methane.pdb
  12 ethane.pdb
  15 propane.pdb
  20 cubane.pdb
  21 pentane.pdb
  30 octane.pdb
 107 total

Podemos entonces enviar esa salida a través de otra pipe, a head, de modo que la pipe completa se convierte en:

BASH

$ wc -l *.pdb | sort -n | head -n 1

SALIDA

   9  methane.pdb

Esto es exactamente como si un matemático anidara funciones como log(3x) y dijera ‘el logaritmo de tres veces x’. En nuestro caso, el algoritmo es ‘cabeza de ordenación del recuento de líneas de *.pdb’.

La redirección y las pipes utilizadas en los últimos comandos se ilustran a continuación:

Redirecciones y pipes de diferentes comandos: "wc -l *.pdb" dirigirá la salida al shell. "wc -l *.pdb > longitudes" dirigirá la salida al archivo "longitudes". "wc -l *.pdb | sort -n | head -n 1" creará un canal en el que la salida del comando "wc" es la entrada del comando "sort", la salida del comando "sort" es la entrada del comando "head" y la salida del comando "head" se dirige al intérprete de órdenes

Uniendo comandos en pipes

En nuestro directorio actual, queremos encontrar los 3 archivos que tienen el menor número de líneas. ¿Qué comando de los que aparecen a continuación funcionaría?

  1. wc -l * > sort -n > head -n 3
  2. wc -l * | sort -n | head -n 1-3
  3. wc -l * | head -n 3 | sort -n
  4. wc -l * | sort -n | head -n 3

La opción 4 es la solución. El carácter | se utiliza para conectar la salida de un comando a la entrada de otro. el carácter > se utiliza para redirigir la salida estándar a un archivo. ¡Pruébalo en el directorio shell-lesson-data/exercise-data/alkanes!

Herramientas diseñadas para trabajar juntas


Esta idea de enlazar programas es la razón por la que Unix ha tenido tanto éxito. En lugar de crear enormes programas que intentan hacer muchas cosas diferentes, los programadores de Unix se centran en crear un montón de herramientas simples que cada una hace bien un trabajo, y que funcionan bien entre sí. Este modelo de programación se denomina “pipes y filtros”. Ya hemos visto las pipes; un filtro es un programa como wc o sort que transforma un flujo de entrada en un flujo de salida. Casi todas las herramientas estándar de Unix pueden trabajar de esta manera. A menos que se les indique lo contrario, leen de la entrada estándar, hacen algo con lo que han leído y escriben en la salida estándar.

La clave es que cualquier programa que lea líneas de texto de la entrada estándar y escriba líneas de texto en la salida estándar puede combinarse con cualquier otro programa que también se comporte de esta manera. Puedes y debes escribir tus programas de esta forma para que tú y otras personas podáis poner esos programas en pipes para multiplicar su potencia.

Comprensión de Lectura de pipes

Un fichero llamado animals.csv (en la carpeta shell-lesson-data/exercise-data/animal-counts) contiene los siguientes datos:

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-06,fox,4
2012-11-07,rabbit,16
2012-11-07,bear,1

¿Qué texto pasa a través de cada una de las pipes y la redirección final en la pipe de abajo? Nota, el comando sort -r ordena en orden inverso.

BASH

$ cat animals.csv | head -n 5 | tail -n 3 | sort -r > final.txt

Pista: construye la pipe comando a comando para comprobar tu comprensión

El comando head extrae las 5 primeras líneas de animals.csv. A continuación, se extraen las 3 últimas líneas de las 5 anteriores mediante el comando tail. Con el comando sort -r esas 3 líneas se ordenan en orden inverso. Finalmente, la salida se redirige a un fichero: final.txt. El contenido de este fichero puede comprobarse ejecutando cat final.txt. El fichero debería contener las siguientes líneas

2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-05,raccoon,7

Construcción de pipes

Para el fichero animals.csv del ejercicio anterior, considera el siguiente comando:

BASH

$ cut -d , -f 2 animals.csv

El comando cut se utiliza para eliminar o ‘cortar’ ciertas secciones de cada línea del fichero, y cut espera que las líneas estén separadas en columnas por un carácter Tab. Un carácter utilizado de esta forma se denomina delimitador. En el ejemplo anterior utilizamos la opción -d para especificar la coma como carácter delimitador. También hemos utilizado la opción -f para especificar que queremos extraer el segundo campo (columna). El resultado es el siguiente

SALIDA

deer
rabbit
raccoon
rabbit
deer
fox
rabbit
bear

El comando uniq filtra las líneas adyacentes coincidentes en un fichero. ¿Cómo podrías extender este conducto (usando uniq y otro comando) para averiguar qué animales contiene el fichero (sin duplicados en sus nombres)?

BASH

$ cut -d , -f 2 animals.csv | sort | uniq

¿Qué pipe?

El fichero animals.csv contiene 8 líneas de datos formateadas de la siguiente manera:

SALIDA

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
...

El comando uniq tiene una opción -c que proporciona un recuento del número de veces que una línea aparece en su entrada. Suponiendo que tu directorio actual es shell-lesson-data/exercise-data/animal-counts, ¿qué comando usarías para producir una tabla que muestre el recuento total de cada tipo de animal en el fichero?

  1. sort animals.csv | uniq -c
  2. sort -t, -k2,2 animals.csv | uniq -c
  3. cut -d, -f 2 animals.csv | uniq -c
  4. cut -d, -f 2 animals.csv | sort | uniq -c
  5. cut -d, -f 2 animals.csv | sort | uniq -c | wc -l

La opción 4. es la respuesta correcta. Si tienes dificultades para entender por qué, intenta ejecutar los comandos, o subsecciones de las pipes (asegúrate de que estás en el directorio shell-lesson-data/exercise-data/animal-counts).

Pipe de Nelle: Comprobación de archivos


Nelle ha pasado sus muestras por las máquinas de ensayo y ha creado 17 archivos en el directorio north-pacific-gyre descrito anteriormente. Como comprobación rápida, partiendo del directorio shell-lesson-data, Nelle teclea:

BASH

$ cd north-pacific-gyre
$ wc -l *.txt

La salida son 18 líneas que tienen este aspecto:

SALIDA

300 NENE01729A.txt
300 NENE01729B.txt
300 NENE01736A.txt
300 NENE01751A.txt
300 NENE01751B.txt
300 NENE01812A.txt
... ...

Ahora escribe esto:

BASH

$ wc -l *.txt | sort -n | head -n 5

SALIDA

 240 NENE02018B.txt
 300 NENE01729A.txt
 300 NENE01729B.txt
 300 NENE01736A.txt
 300 NENE01751A.txt

Ups: uno de los ficheros es 60 líneas más corto que los otros. Cuando vuelve y lo comprueba, ve que hizo ese ensayo a las 8:00 de un lunes por la mañana — probablemente alguien estuvo usando la máquina el fin de semana, y se olvidó de reiniciarla. Antes de volver a ejecutar esa muestra, comprueba si algún archivo tiene demasiados datos:

BASH

$ wc -l *.txt | sort -n | tail -n 5

SALIDA

 300 NENE02040B.txt
 300 NENE02040Z.txt
 300 NENE02043A.txt
 300 NENE02043B.txt
5040 total

Esos números tienen buena pinta — pero ¿qué hace esa ‘Z’ ahí en la antepenúltima línea? Todas sus muestras deberían estar marcadas con ‘A’ o ‘B’; por convención, su laboratorio utiliza la ‘Z’ para indicar las muestras a las que les falta información. Para encontrar otras iguales, hace esto:

BASH

$ ls *Z.txt

SALIDA

NENE01971Z.txt    NENE02040Z.txt

Efectivamente, cuando comprueba el registro en su portátil, no hay ninguna profundidad registrada para ninguna de esas muestras. Como es demasiado tarde para obtener la información de otra forma, debe excluir esos dos archivos de su análisis. Podría eliminarlos utilizando rm, pero hay algunos análisis que podría hacer más adelante en los que la profundidad no importa, así que en su lugar, tendrá que tener cuidado más adelante para seleccionar los archivos utilizando las expresiones comodín NENE*A.txt NENE*B.txt.

Eliminar archivos innecesarios

Supongamos que desea eliminar los archivos de datos procesados y conservar sólo los archivos sin procesar y el script de procesamiento para ahorrar espacio. Los archivos sin procesar terminan en .dat y los archivos procesados terminan en .txt. ¿Cuál de las siguientes opciones eliminaría todos los ficheros de datos procesados, y sólo los ficheros de datos procesados?

  1. rm ?.txt
  2. rm *.txt
  3. rm * .txt
  4. rm *.*
  1. Esto eliminaría los archivos .txt con nombres de un carácter
  2. Esta es la respuesta correcta
  3. El shell expandiría * para coincidir con todo lo que hay en el directorio actual, por lo que el comando intentaría eliminar todos los ficheros coincidentes y un fichero adicional llamado .txt
  4. El shell expande *.* para que coincida con todos los nombres de fichero que contengan al menos un ., incluyendo los ficheros procesados (.txt) y los ficheros sin procesar (.dat)

Puntos Clave

  • wc cuenta líneas, palabras y caracteres en sus entradas.
  • cat muestra el contenido de sus entradas.
  • sort ordena sus entradas.
  • head muestra por defecto las 10 primeras líneas de su entrada sin argumentos adicionales.
  • tail muestra por defecto las 10 últimas líneas de su entrada sin argumentos adicionales.
  • command > [file] redirige la salida de un comando a un archivo (sobreescribiendo cualquier contenido existente).
  • command >> [file] añade la salida de un comando a un archivo.
  • [first] | [second] es una pipe: la salida del primer comando se usa como entrada del segundo.
  • La mejor manera de usar el shell es usar pipes para combinar programas simples de un solo propósito (filtros).

Content from Bucles


Última actualización: 2025-04-03 | Mejora esta página

Hoja de ruta

Preguntas

  • ¿Cómo puedo realizar las mismas acciones en muchos archivos diferentes?

Objetivos

  • Escribe un bucle que aplique uno o más comandos por separado a cada fichero de un conjunto de ficheros.
  • Trazar los valores que toma una variable del bucle durante la ejecución del mismo.
  • Explica la diferencia entre el nombre de una variable y su valor.
  • Explique por qué los espacios y algunos caracteres de puntuación no deben usarse en los nombres de archivo.
  • Demuestra cómo ver qué comandos se han ejecutado recientemente.
  • Reejecuta los comandos ejecutados recientemente sin volver a escribirlos.

Los bucles son una construcción de programación que nos permite repetir un comando o conjunto de comandos para cada elemento de una lista. Como tales, son clave para mejorar la productividad mediante la automatización. Al igual que los comodines y el tabulador, el uso de bucles también reduce la cantidad de texto necesario (y, por tanto, el número de errores tipográficos).

Supongamos que tenemos varios cientos de archivos de datos genómicos llamados basilisk.dat, minotaur.dat y unicorn.dat. Para este ejemplo, utilizaremos el directorio exercise-data/creatures que sólo tiene tres ficheros de ejemplo, pero los principios pueden aplicarse a muchos más ficheros a la vez.

La estructura de estos ficheros es la misma: el nombre común, la clasificación y la fecha de actualización se presentan en las tres primeras líneas, con las secuencias de ADN en las líneas siguientes. Veamos los ficheros

BASH

$ head -n 5 basilisk.dat minotaur.dat unicorn.dat

Queremos imprimir la clasificación de cada especie, que aparece en la segunda línea de cada fichero. Para cada archivo, tendríamos que ejecutar el comando head -n 2 y canalizarlo a tail -n 1. Utilizaremos un bucle para resolver este problema, pero primero veamos la forma general de un bucle, utilizando el pseudocódigo siguiente:

BASH

# The word "for" indicates the start of a "For-loop" command
for thing in list_of_things 
#The word "do" indicates the start of job execution list
do 
    # Indentation within the loop is not required, but aids legibility
    operation_using/command $thing 
# The word "done" indicates the end of a loop
done  

y podemos aplicarlo a nuestro ejemplo de la siguiente manera:

BASH

$ for filename in basilisk.dat minotaur.dat unicorn.dat
> do
>     echo $filename
>     head -n 2 $filename | tail -n 1
> done

SALIDA

basilisk.dat
CLASSIFICATION: basiliscus vulgaris
minotaur.dat
CLASSIFICATION: bos hominus
unicorn.dat
CLASSIFICATION: equus monoceros

Siga las instrucciones

El prompt del shell cambia de $ a > y viceversa mientras escribíamos en nuestro bucle. El segundo prompt, >, es diferente para recordarnos que aún no hemos terminado de escribir un comando completo. Se puede utilizar un punto y coma, ;, para separar dos órdenes escritas en una sola línea.

Cuando el shell ve la palabra clave for, sabe que debe repetir un comando (o grupo de comandos) una vez por cada elemento de una lista. Cada vez que se ejecuta el bucle (lo que se denomina una iteración), se asigna un elemento de la lista en secuencia a la variable, y se ejecutan los comandos dentro del bucle, antes de pasar al siguiente elemento de la lista. Dentro del bucle, pedimos el valor de la variable poniendo $ delante de ella. El $ le dice al intérprete del shell que trate la variable como un nombre de variable y sustituya su valor en su lugar, en lugar de tratarlo como texto o un comando externo.

En este ejemplo, la lista es de tres nombres de fichero: basilisk.dat, minotaur.dat, y unicorn.dat. Cada vez que el bucle itera, primero usamos echo para imprimir el valor que tiene actualmente la variable $filename. Esto no es necesario para el resultado, pero nos beneficia aquí para seguirlo más fácilmente. A continuación, ejecutaremos el comando head en el fichero referenciado actualmente por $filename. La primera vez a través del bucle, $filename es basilisk.dat. El intérprete ejecuta el comando head en basilisk.dat y envía las dos primeras líneas al comando tail, que a su vez imprime la segunda línea de basilisk.dat. Para la segunda iteración, $filename se convierte en minotaur.dat. Esta vez, el shell ejecuta head en minotaur.dat y envía las dos primeras líneas al comando tail, que imprime la segunda línea de minotaur.dat. Para la tercera iteración, $filename se convierte en unicorn.dat, así que el shell ejecuta el comando head en ese fichero, y tail en la salida del mismo. Como la lista sólo tenía tres elementos, la shell sale del bucle for.

Mismos símbolos, diferentes significados

Aquí vemos que > se utiliza como prompt del shell, mientras que > también se utiliza para redirigir la salida. De forma similar, $ se usa como prompt del shell, pero, como vimos antes, también se usa para pedir al shell que obtenga el valor de una variable.

Si el shell imprime > o $ entonces espera que escribas algo, y el símbolo es un prompt.

Si usted mismo teclea > o $, es una instrucción suya para que el shell redirija la salida u obtenga el valor de una variable.

Cuando se usan variables también es posible poner los nombres entre llaves para delimitar claramente el nombre de la variable: $filename es equivalente a ${filename}, pero es diferente de ${file}name. Puedes encontrar esta notación en los programas de otras personas.

Hemos llamado a la variable en este bucle filename para que su propósito sea más claro para los lectores humanos. Al shell en sí no le importa cómo se llama la variable; si escribiéramos este bucle como:

BASH

$ for x in basilisk.dat minotaur.dat unicorn.dat
> do
>     head -n 2 $x | tail -n 1
> done

o:

BASH

$ for temperature in basilisk.dat minotaur.dat unicorn.dat
> do
>     head -n 2 $temperature | tail -n 1
> done

funcionaría exactamente igual. No haga esto. Los programas sólo son útiles si la gente puede entenderlos, así que los nombres sin sentido (como x) o los nombres engañosos (como temperature) aumentan las probabilidades de que el programa no haga lo que sus lectores piensan que hace.

En los ejemplos anteriores, las variables (thing, filename, x y temperature) podrían haber recibido cualquier otro nombre, siempre que sea significativo tanto para la persona que escribe el código como para la que lo lee.

Observe también que los bucles pueden usarse para otras cosas que no sean nombres de ficheros, como una lista de números o un subconjunto de datos.

Escribe tu propio bucle

¿Cómo escribirías un bucle que se haga eco de los 10 números del 0 al 9?

BASH

$ for loop_variable in 0 1 2 3 4 5 6 7 8 9
> do
>     echo $loop_variable
> done

SALIDA

0
1
2
3
4
5
6
7
8
9

Variables en bucles

Este ejercicio se refiere al directorio shell-lesson-data/exercise-data/alkanes.ls *.pdb da la siguiente salida:

SALIDA

cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb

¿Cuál es la salida del siguiente código?

BASH

$ for datafile in *.pdb
> do
>     ls *.pdb
> done

Ahora, ¿cuál es la salida del siguiente código?

BASH

$ for datafile in *.pdb
> do
>     ls $datafile
> done

¿Por qué estos dos bucles dan salidas diferentes?

El primer bloque de código da la misma salida en cada iteración del bucle. Bash expande el comodín *.pdb dentro del cuerpo del bucle (así como antes de que comience el bucle) para que coincida con todos los archivos que terminan en .pdb y luego los enumera utilizando ls. El bucle expandido tendría este aspecto

BASH

$ for datafile in cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb
> do
>     ls cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb
> done

SALIDA

cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb
cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb
cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb
cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb
cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb
cubane.pdb  ethane.pdb  methane.pdb  octane.pdb  pentane.pdb  propane.pdb

El segundo bloque de código lista un fichero diferente en cada iteración del bucle. El valor de la variable datafile se evalúa con $datafile, y luego se lista con ls.

SALIDA

cubane.pdb
ethane.pdb
methane.pdb
octane.pdb
pentane.pdb
propane.pdb

Limitación de conjuntos de ficheros

¿Cuál sería el resultado de ejecutar el siguiente bucle en el directorio shell-lesson-data/exercise-data/alkanes?

BASH

$ for filename in c*
> do
>     ls $filename
> done
  1. No hay ficheros listados.
  2. Se listan todos los ficheros.
  3. Sólo aparecen cubane.pdb, octane.pdb y pentane.pdb.
  4. Sólo aparece cubane.pdb.

4 es la respuesta correcta.* coincide con cero o más caracteres, por lo que cualquier nombre de archivo que empiece por la letra c, seguida de cero o más caracteres será coincidente.

Limitación de conjuntos de ficheros (continued)

¿En qué se diferenciaría la salida de este comando?

BASH

$ for filename in *c*
> do
>     ls $filename
> done
  1. Se listarían los mismos ficheros.
  2. Esta vez se listan todos los ficheros.
  3. No hay ficheros listados esta vez.
  4. Se listarán los ficheros cubane.pdb y octane.pdb.
  5. Sólo se listará el fichero octane.pdb.

4 es la respuesta correcta. * coincide con cero o más caracteres, por lo que un nombre de archivo con cero o más caracteres antes de la letra c y cero o más caracteres después de la letra c será coincidente.

Guardar en un archivo en un bucle - Primera parte

En el directorio shell-lesson-data/exercise-data/alkanes, ¿cuál es el efecto de este bucle?

BASH

for alkanes in *.pdb
do
    echo $alkanes
    cat $alkanes > alkanes.pdb
done
  1. Imprime cubane.pdb, ethane.pdb, methane.pdb, octane.pdb, pentane.pdb y propane.pdb, y el texto de propane.pdb se guardará en un fichero llamado alkanes.pdb.
  2. Imprime cubane.pdb, ethane.pdb, y methane.pdb, y el texto de los tres ficheros se concatena y se guarda en un fichero llamado alkanes.pdb.
  3. Imprime cubane.pdb, ethane.pdb, methane.pdb, octane.pdb, y pentane.pdb, y el texto de propane.pdb se guardará en un fichero llamado alkanes.pdb.
  4. Ninguna de las anteriores.
  1. El texto de cada fichero se escribe en el fichero alkanes.pdb. Sin embargo, el fichero se sobreescribe en cada iteración del bucle, por lo que el contenido final de alkanes.pdb es el texto del fichero propane.pdb.

Guardar en un archivo en un bucle - Segunda parte

También en el directorio shell-lesson-data/exercise-data/alkanes, ¿cuál sería la salida del siguiente bucle?

BASH

for datafile in *.pdb
do
    cat $datafile >> all.pdb
done
  1. Todo el texto de cubane.pdb, ethane.pdb, methane.pdb, octane.pdb, y pentane.pdb sería concatenado y guardado en un fichero llamado all.pdb.
  2. El texto de ethane.pdb se guardará en un fichero llamado all.pdb.
  3. Todo el texto de cubane.pdb, ethane.pdb, methane.pdb, octane.pdb, pentane.pdb y propane.pdb sería concatenado y guardado en un fichero llamado all.pdb.
  4. Todo el texto de cubane.pdb, ethane.pdb, methane.pdb, octane.pdb, pentane.pdb y propane.pdb se imprimiría en la pantalla y se guardaría en un fichero llamado all.pdb.

3 es la respuesta correcta. >> añade a un archivo, en lugar de sobrescribirlo con la salida redirigida de un comando. Dado que la salida del comando cat ha sido redirigida, no se imprime nada en la pantalla.

Continuemos con nuestro ejemplo en el directorio shell-lesson-data/exercise-data/creatures. Aquí tenemos un bucle un poco más complicado:

BASH

$ for filename in *.dat
> do
>     echo $filename
>     head -n 100 $filename | tail -n 20
> done

El shell comienza expandiendo *.dat para crear la lista de ficheros que procesará. A continuación, el cuerpo de bucle ejecuta dos comandos para cada uno de esos ficheros. El primer comando, echo, imprime sus argumentos de línea de comandos en la salida estándar. Por ejemplo

BASH

$ echo hello there

imprime:

SALIDA

hello there

En este caso, como el shell expande $filename para que sea el nombre de un fichero, echo $filename imprime el nombre del fichero. Tenga en cuenta que no podemos escribir esto como:

BASH

$ for filename in *.dat
> do
>     $filename
>     head -n 100 $filename | tail -n 20
> done

porque entonces la primera vez a través del bucle, cuando $filename se expandió a basilisk.dat, el shell intentaría ejecutar basilisk.dat como un programa. Finalmente, la combinación head y tail selecciona las líneas 81-100 de cualquier fichero que esté siendo procesado (asumiendo que el fichero tiene al menos 100 líneas).

Espacios en los nombres

Los espacios se utilizan para separar los elementos de la lista que vamos a recorrer. Si uno de esos elementos contiene un espacio, debemos rodearlo de comillas y hacer lo mismo con nuestra variable de bucle. Supongamos que nuestros ficheros de datos se llaman

red dragon.dat
purple unicorn.dat

Para hacer un bucle sobre estos ficheros, tendríamos que añadir comillas dobles de la siguiente manera:

BASH

$ for filename in "red dragon.dat" "purple unicorn.dat"
> do
>     head -n 100 "$filename" | tail -n 20
> done

Es más sencillo evitar el uso de espacios (u otros caracteres especiales) en los nombres de archivo.

Los ficheros anteriores no existen, por lo que si ejecutamos el código anterior, el comando head será incapaz de encontrarlos; sin embargo, el mensaje de error devuelto mostrará el nombre de los ficheros que está esperando:

ERROR

head: cannot open ‘red dragon.dat' for reading: No such file or directory
head: cannot open ‘purple unicorn.dat' for reading: No such file or directory

Intente eliminar las comillas alrededor de $filename en el bucle anterior para ver el efecto de las comillas sobre los espacios. Observe que obtenemos un resultado del comando de bucle para unicorn.dat cuando ejecutamos este código en el directorio creatures:

SALIDA

head: cannot open ‘red' for reading: No such file or directory
head: cannot open ‘dragon.dat' for reading: No such file or directory
head: cannot open ‘purple' for reading: No such file or directory
CGGTACCGAA
AAGGGTCGCG
CAAGTGTTCC
...

Queremos modificar cada uno de los ficheros de shell-lesson-data/exercise-data/creatures, pero también guardar una versión de los ficheros originales. Queremos copiar los ficheros originales en nuevos ficheros llamados original-basilisk.dat y original-unicorn.dat, por ejemplo. No podemos utilizar

BASH

$ cp *.dat original-*.dat

porque se expandiría a:

BASH

$ cp basilisk.dat minotaur.dat unicorn.dat original-*.dat

Esto no respaldaría nuestros archivos, en su lugar obtendríamos un error:

ERROR

cp: target `original-*.dat' is not a directory

Este problema surge cuando cp recibe más de dos entradas. Cuando esto ocurre, espera que la última entrada sea un directorio en el que pueda copiar todos los archivos que se le han pasado. Como no hay ningún directorio llamado original-*.dat en el directorio creatures, obtenemos un error.

En su lugar, podemos utilizar un bucle:

BASH

$ for filename in *.dat
> do
>     cp $filename original-$filename
> done

Este bucle ejecuta el comando cp una vez por cada nombre de fichero. La primera vez, cuando $filename se expande a basilisk.dat, el shell ejecuta:

BASH

cp basilisk.dat original-basilisk.dat

La segunda vez, el comando es:

BASH

cp minotaur.dat original-minotaur.dat

La tercera y última vez, el comando es:

BASH

cp unicorn.dat original-unicorn.dat

Como el comando cp normalmente no produce ninguna salida, es difícil comprobar que el bucle funciona correctamente. Sin embargo, aprendimos antes cómo imprimir cadenas usando echo, y podemos modificar el bucle para usar echo para imprimir nuestros comandos sin ejecutarlos realmente. Así podemos comprobar qué comandos se ejecutarían en el bucle sin modificar.

El siguiente diagrama muestra lo que ocurre cuando se ejecuta el bucle modificado y demuestra cómo el uso juicioso de echo es una buena técnica de depuración.

El bucle for "for filename in .dat; do echo cp $filename original-$filename;done" asignará sucesivamente los nombres de todos los archivos ".dat" del directorio actual a la variable "$filename" y, a continuación, ejecutará el comando. Con los archivos "basilisk.dat", "minotaur.dat" y "unicorn.dat" en el directorio actual, el bucle llamará sucesivamente al comando echo tres veces e imprimirá tres líneas: "cp baselisk.dat original-basilisk.dat", luego "cp minotaur.datoriginal-minotaur.dat" y finalmente "cp unicorn.datoriginal-unicorn.dat"

Nelle’s Pipeline: Procesamiento de archivos


Nelle está ahora lista para procesar sus ficheros de datos usando goostats.sh — un script escrito por su supervisor. Esto calcula algunas estadísticas de un archivo de muestra de proteína y toma dos argumentos:

  1. un archivo de entrada (que contiene los datos en bruto)
  2. un archivo de salida (para almacenar las estadísticas calculadas)

Como todavía está aprendiendo a usar el shell, decide construir los comandos necesarios por etapas. Su primer paso es asegurarse de que puede seleccionar los archivos de entrada correctos — recuerde, son aquellos cuyos nombres terminan en ‘A’ o ‘B’, en lugar de ‘Z’. En el directorio north-pacific-gyre, Nelle escribe:

BASH

$ cd
$ cd Desktop/shell-lesson-data/north-pacific-gyre
$ for datafile in NENE*A.txt NENE*B.txt
> do
>     echo $datafile
> done

SALIDA

NENE01729A.txt
NENE01736A.txt
NENE01751A.txt

...
NENE02040B.txt
NENE02043B.txt

Su siguiente paso es decidir cómo llamar a los archivos que creará el programa de análisis goostats.sh. Prefijar el nombre de cada archivo de entrada con ‘stats’ parece sencillo, así que modifica su bucle para hacerlo:

BASH

$ for datafile in NENE*A.txt NENE*B.txt
> do
>     echo $datafile stats-$datafile
> done

SALIDA

NENE01729A.txt stats-NENE01729A.txt
NENE01736A.txt stats-NENE01729A.txt
NENE01751A.txt stats-NENE01729A.txt
...
NENE02040B.txt stats-NENE02040B.txt
NENE02043B.txt stats-NENE02043B.txt

Todavía no ha ejecutado goostats.sh, pero ahora está segura de que puede seleccionar los archivos correctos y generar los nombres de archivo de salida correctos.

Sin embargo, escribir los comandos una y otra vez se está volviendo tedioso, y a Nelle le preocupa cometer errores, así que en lugar de volver a entrar en su bucle, pulsa . En respuesta, el intérprete de comandos vuelve a mostrar todo el bucle en una línea (utilizando punto y coma para separar las piezas):

BASH

$ for datafile in NENE*A.txt NENE*B.txt; do echo $datafile stats-$datafile; done

Usando el , Nelle navega hasta el comando echo y lo cambia por bash goostats.sh:

BASH

$ for datafile in NENE*A.txt NENE*B.txt; do bash goostats.sh $datafile stats-$datafile; done

Cuando pulsa Enter, el intérprete de comandos ejecuta el comando modificado. Sin embargo, no parece ocurrir nada: no hay salida. Después de un momento, Nelle se da cuenta de que como su script ya no imprime nada en la pantalla, no tiene ni idea de si se está ejecutando, y mucho menos de lo rápido que lo está haciendo. Ella mata el comando en ejecución escribiendo Ctrl+C, utiliza para repetir el comando, y lo edita para que se lea:

BASH

$ for datafile in NENE*A.txt NENE*B.txt; do echo $datafile;
bash goostats.sh $datafile stats-$datafile; done

Principio y Fin

Podemos movernos al principio de una línea en el shell tecleando Ctrl+A y al final usando Ctrl+E.

Cuando ejecuta su programa ahora, produce una línea de salida cada cinco segundos más o menos:

SALIDA

NENE01729A.txt
NENE01736A.txt
NENE01751A.txt
...

1518 veces 5 segundos, dividido por 60, le dice que su script tardará unas dos horas en ejecutarse. Como última comprobación, abre otra ventana de terminal, entra en north-pacific-gyre y utiliza cat stats-NENE01729B.txt para examinar uno de los archivos de salida. Tiene buena pinta, así que decide tomarse un café y ponerse al día con la lectura.

Los que conocen la historia pueden elegir repetirla

Otra forma de repetir el trabajo anterior es utilizar el comando history para obtener una lista de los últimos cientos de comandos que se han ejecutado, y luego utilizar !123 (donde 123 se sustituye por el número de comando) para repetir uno de esos comandos. Por ejemplo, si Nelle escribe esto

BASH

$ history | tail -n 5

SALIDA

456  for datafile in NENE*A.txt NENE*B.txt; do   echo $datafile stats-$datafile; done
457  for datafile in NENE*A.txt NENE*B.txt; do echo $datafile stats-$datafile; done
458  for datafile in NENE*A.txt NENE*B.txt; do bash goostats.sh $datafile stats-$datafile; done
459  for datafile in NENE*A.txt NENE*B.txt; do echo $datafile; bash goostats.sh $datafile
stats-$datafile; done
460  history | tail -n 5

entonces puede volver a ejecutar goostats.sh en los archivos simplemente escribiendo !459.

Otros comandos del historial

Existen otros comandos de acceso directo para acceder al historial.

  • Ctrl+R entra en un modo de búsqueda en el historial ‘reverse-i-search’ y encuentra el comando más reciente de tu historial que coincida con el texto que introduzcas a continuación. Pulsa Ctrl+R una o varias veces más para buscar coincidencias anteriores. A continuación, puede utilizar las teclas de flecha izquierda y derecha para elegir esa línea y editarla y, a continuación, pulsar Return para ejecutar el comando.
  • !! recupera el comando inmediatamente anterior (puede o no encontrar esto más conveniente que usar )
  • !$ recupera la última palabra de la última orden. Esto es útil más a menudo de lo que esperas: después de bash goostats.sh NENE01729B.txt stats-NENE01729B.txt, puedes teclear less !$ para ver el fichero stats-NENE01729B.txt, lo que es más rápido que hacer y editar la línea de órdenes.

Ejecución en seco

Un bucle es una forma de hacer muchas cosas a la vez — o de cometer muchos errores a la vez si hace lo incorrecto. Una forma de comprobar lo que un bucle haría es echo los comandos que ejecutaría en lugar de ejecutarlos realmente.

Supongamos que queremos previsualizar los comandos que ejecutará el siguiente bucle sin ejecutar realmente esos comandos:

BASH

$ for datafile in *.pdb
> do
>     cat $datafile >> all.pdb
> done

¿Cuál es la diferencia entre los dos bucles de abajo, y cuál de ellos queremos ejecutar?

BASH

# Version 1
$ for datafile in *.pdb
> do
>     echo cat $datafile >> all.pdb
> done

BASH

# Version 2
$ for datafile in *.pdb
> do
>     echo "cat $datafile >> all.pdb"
> done

La segunda versión es la que queremos ejecutar. Esto imprime en pantalla todo lo que está entre comillas, expandiendo el nombre de la variable de bucle porque lo hemos prefijado con un signo de dólar. Tampoco modifica ni crea el fichero all.pdb, ya que el >> se trata literalmente como parte de una cadena y no como una instrucción de redirección.

La primera versión añade la salida del comando echo cat $datafile al fichero, all.pdb. Este fichero sólo contendrá la lista: cat cubane.pdb, cat ethane.pdb, cat methane.pdb etc.

¡Pruebe usted mismo ambas versiones para ver el resultado! Asegúrese de abrir el archivo all.pdb para ver su contenido.

Bucles anidados

Supongamos que queremos establecer una estructura de directorios para organizar algunos experimentos que miden constantes de velocidad de reacción con diferentes compuestos y diferentes temperaturas. ¿Cuál sería el resultado del siguiente código:

BASH

$ for species in cubane ethane methane
> do
>     for temperature in 25 30 37 40
>     do
>         mkdir $species-$temperature
>     done
> done

Tenemos un bucle anidado, es decir, contenido dentro de otro bucle, por lo que para cada especie en el bucle exterior, el bucle interior (el bucle anidado) itera sobre la lista de temperaturas, y crea un nuevo directorio para cada combinación.

¡Intente ejecutar el código usted mismo para ver qué directorios se crean!

Puntos Clave

  • Un bucle for repite los comandos una vez por cada cosa de una lista.
  • Cada bucle for necesita una variable para referirse a lo que está operando en ese momento.
  • Utilice $name para expandir una variable (es decir, obtener su valor). también se puede utilizar ${name}.
  • No utilice espacios, comillas o caracteres comodín como ‘*’ o ‘?’ en los nombres de fichero, ya que complica la expansión de variables.
  • Dar a los ficheros nombres consistentes que sean fáciles de emparejar con patrones comodín para facilitar su selección para el bucle.
  • Utilice la tecla de flecha arriba para desplazarse por los comandos anteriores para editarlos y repetirlos.
  • Utilice Ctrl+R para buscar entre los comandos introducidos anteriormente.
  • Usa history para mostrar comandos recientes, y ![number] para repetir un comando por número.

Content from Scripts de shell


Última actualización: 2025-04-03 | Mejora esta página

Hoja de ruta

Preguntas

  • ¿Cómo puedo guardar y reutilizar comandos?

Objetivos

  • Escribe un script de shell que ejecute un comando o una serie de comandos para un conjunto fijo de archivos.
  • Ejecuta un script de shell desde la línea de comandos.
  • Escribe un script de shell que opera sobre un conjunto de ficheros definidos por el usuario en la línea de comandos.
  • Crear pipelines que incluyan shell scripts que tú, y otros, hayáis escrito.

Por fin estamos listos para ver por qué el shell es un entorno de programación tan potente. Vamos a tomar los comandos que repetimos con frecuencia y guardarlos en archivos para que podamos volver a ejecutar todas esas operaciones más tarde escribiendo un solo comando. Por razones históricas, un montón de comandos guardados en un archivo se suele llamar script de shell, pero no te equivoques: en realidad son pequeños programas.

Escribir secuencias de comandos no sólo agilizará el trabajo, sino que también evitará tener que volver a escribir los mismos comandos una y otra vez. También lo hará más preciso (menos posibilidades de errores tipográficos) y más reproducible. Si vuelves a tu trabajo más tarde (o si alguien más encuentra tu trabajo y quiere basarse en él), podrás reproducir los mismos resultados simplemente ejecutando tu script, en lugar de tener que recordar o volver a escribir una larga lista de comandos.

Empecemos volviendo a alkanes/ y creando un nuevo fichero, middle.sh que se convertirá en nuestro shell script:

BASH

$ cd alkanes
$ nano middle.sh

El comando nano middle.sh abre el archivo middle.sh dentro del editor de texto ‘nano’ (que se ejecuta dentro del shell). Si el fichero no existe, se creará. Podemos utilizar el editor de texto para editar directamente el archivo insertando la siguiente línea:

head -n 15 octane.pdb | tail -n 5

Esta es una variación de la tubería que construimos antes, que selecciona las líneas 11-15 del archivo octane.pdb. Recuerda, no estamos ejecutándolo como un comando todavía; sólo estamos incorporando los comandos en un archivo.

Luego guardamos el archivo (Ctrl-O en nano) y salimos del editor de texto (Ctrl-X en nano). Comprueba que el directorio alkanes contiene ahora un fichero llamado middle.sh.

Una vez guardado el fichero, podemos pedir al intérprete de órdenes que ejecute los comandos que contiene. Nuestro shell se llama bash, así que ejecutamos el siguiente comando:

BASH

$ bash middle.sh

SALIDA

ATOM      9  H           1      -4.502   0.681   0.785  1.00  0.00
ATOM     10  H           1      -5.254  -0.243  -0.537  1.00  0.00
ATOM     11  H           1      -4.357   1.252  -0.895  1.00  0.00
ATOM     12  H           1      -3.009  -0.741  -1.467  1.00  0.00
ATOM     13  H           1      -3.172  -1.337   0.206  1.00  0.00

Efectivamente, la salida de nuestro script es exactamente la que obtendríamos si ejecutáramos ese pipeline directamente.

Texto vs. Lo que sea

Solemos llamar “editores de texto” a programas como Microsoft Word o LibreOffice Writer, pero tenemos que ser un poco más cuidadosos cuando se trata de programar. Por defecto, Microsoft Word utiliza archivos .docx para almacenar no sólo texto, sino también información de formato sobre fuentes, encabezados, etcétera. Esta información adicional no se almacena como caracteres y no significa nada para herramientas como head, que espera que los archivos de entrada no contengan más que las letras, dígitos y signos de puntuación de un teclado de ordenador estándar. Por lo tanto, al editar programas, debe utilizar un editor de texto sin formato o tener cuidado de guardar los archivos como texto sin formato.

¿Y si queremos seleccionar líneas de un archivo arbitrario? Podríamos editar middle.sh cada vez para cambiar el nombre del archivo, pero eso probablemente nos llevaría más tiempo que volver a escribir el comando en el intérprete de comandos y ejecutarlo con un nuevo nombre de archivo. En su lugar, vamos a editar middle.sh y hacerlo más versátil:

BASH

$ nano middle.sh

Ahora, dentro de “nano”, sustituye el texto octane.pdb por la variable especial llamada $1:

head -n 15 "$1" | tail -n 5

Dentro de un script de shell, $1 significa ‘el primer nombre de archivo (u otro argumento) en la línea de comandos’. Ahora podemos ejecutar nuestro script así

BASH

$ bash middle.sh octane.pdb

SALIDA

ATOM      9  H           1      -4.502   0.681   0.785  1.00  0.00
ATOM     10  H           1      -5.254  -0.243  -0.537  1.00  0.00
ATOM     11  H           1      -4.357   1.252  -0.895  1.00  0.00
ATOM     12  H           1      -3.009  -0.741  -1.467  1.00  0.00
ATOM     13  H           1      -3.172  -1.337   0.206  1.00  0.00

o en un archivo diferente como este:

BASH

$ bash middle.sh pentane.pdb

SALIDA

ATOM      9  H           1       1.324   0.350  -1.332  1.00  0.00
ATOM     10  H           1       1.271   1.378   0.122  1.00  0.00
ATOM     11  H           1      -0.074  -0.384   1.288  1.00  0.00
ATOM     12  H           1      -0.048  -1.362  -0.205  1.00  0.00
ATOM     13  H           1      -1.183   0.500  -1.412  1.00  0.00

Comillas dobles alrededor de los argumentos

Por la misma razón que ponemos la variable de bucle dentro de comillas dobles, en caso de que el nombre del fichero contenga espacios, rodeamos $1 con comillas dobles.

Actualmente, tenemos que editar middle.sh cada vez que queremos ajustar el rango de líneas que se devuelve. Vamos a solucionarlo configurando nuestro script para que utilice tres argumentos de línea de comandos. Después del primer argumento de línea de órdenes ($1), cada argumento adicional que proporcionemos será accesible a través de las variables especiales $1, $2, $3, que se refieren al primer, segundo y tercer argumento de línea de órdenes, respectivamente.

Sabiendo esto, podemos usar argumentos adicionales para definir el rango de líneas a pasar a head y tail respectivamente:

BASH

$ nano middle.sh
head -n "$2" "$1" | tail -n "$3"

Ahora podemos ejecutar:

BASH

$ bash middle.sh pentane.pdb 15 5

SALIDA

ATOM      9  H           1       1.324   0.350  -1.332  1.00  0.00
ATOM     10  H           1       1.271   1.378   0.122  1.00  0.00
ATOM     11  H           1      -0.074  -0.384   1.288  1.00  0.00
ATOM     12  H           1      -0.048  -1.362  -0.205  1.00  0.00
ATOM     13  H           1      -1.183   0.500  -1.412  1.00  0.00

Cambiando los argumentos de nuestro comando, podemos cambiar el comportamiento de nuestro script:

BASH

$ bash middle.sh pentane.pdb 20 5

SALIDA

ATOM     14  H           1      -1.259   1.420   0.112  1.00  0.00
ATOM     15  H           1      -2.608  -0.407   1.130  1.00  0.00
ATOM     16  H           1      -2.540  -1.303  -0.404  1.00  0.00
ATOM     17  H           1      -3.393   0.254  -0.321  1.00  0.00
TER      18              1

Esto funciona, pero puede que la próxima persona que lea middle.sh tarde un momento en darse cuenta de lo que hace. Podemos mejorar nuestro script añadiendo algunos comentarios en la parte superior:

BASH

$ nano middle.sh
# Select lines from the middle of a file.
# Usage: bash middle.sh filename end_line num_lines
head -n "$2" "$1" | tail -n "$3"

Un comentario empieza con un carácter # y llega hasta el final de la línea. El ordenador ignora los comentarios, pero tienen un valor incalculable para ayudar a la gente (incluido tu futuro yo) a entender y utilizar los scripts. La única advertencia es que cada vez que modifiques el script, debes comprobar que el comentario sigue siendo correcto. Una explicación que lleve al lector en la dirección equivocada es peor que ninguna.

¿Qué pasa si queremos procesar muchos archivos en un solo proceso? Por ejemplo, si queremos ordenar nuestros archivos .pdb por longitud, escribiríamos:

BASH

$ wc -l *.pdb | sort -n

porque wc -l lista el número de líneas en los ficheros (recuerde que wc significa ‘recuento de palabras’, añadiendo la opción -l significa ‘recuento de líneas’ en su lugar) y sort -n ordena las cosas numéricamente. Podríamos poner esto en un fichero, pero entonces sólo ordenaría una lista de ficheros .pdb en el directorio actual. Si queremos obtener una lista ordenada de otros tipos de ficheros, necesitamos una forma de introducir todos esos nombres en el script. No podemos usar $1, $2, etc. porque no sabemos cuántos ficheros hay. En su lugar, usamos la variable especial $@, que significa, ‘Todos los argumentos de línea de comandos del script de shell’. También debemos poner $@ entre comillas dobles para manejar el caso de argumentos que contienen espacios ("$@" es sintaxis especial y es equivalente a "$1" "$2" …).

He aquí un ejemplo:

BASH

$ nano sorted.sh
# Sort files by their length.
# Usage: bash sorted.sh one_or_more_filenames
wc -l "$@" | sort -n

BASH

$ bash sorted.sh *.pdb ../creatures/*.dat

SALIDA

9 methane.pdb
12 ethane.pdb
15 propane.pdb
20 cubane.pdb
21 pentane.pdb
30 octane.pdb
163 ../creatures/basilisk.dat
163 ../creatures/minotaur.dat
163 ../creatures/unicorn.dat
596 total

Lista de especies únicas

Leah tiene varios cientos de ficheros de datos, cada uno de los cuales tiene el siguiente formato:

2013-11-05,deer,5
2013-11-05,rabbit,22
2013-11-05,raccoon,7
2013-11-06,rabbit,19
2013-11-06,deer,2
2013-11-06,fox,1
2013-11-07,rabbit,18
2013-11-07,bear,1

Un ejemplo de este tipo de fichero se da en shell-lesson-data/exercise-data/animal-counts/animals.csv.

Podemos utilizar el comando cut -d , -f 2 animals.csv | sort | uniq para producir las especies únicas en animals.csv. Para evitar tener que teclear esta serie de comandos cada vez, un científico puede optar por escribir un script de shell en su lugar.

Escribe un script de shell llamado species.sh que tome cualquier número de nombres de archivo como argumentos de línea de comandos y utilice una variación del comando anterior para imprimir una lista de las especies únicas que aparecen en cada uno de esos archivos por separado.

BASH

# Script to find unique species in csv files where species is the second data field
# This script accepts any number of file names as command line arguments

# Loop over all files
for file in $@
do
    echo "Unique species in $file:"
    # Extract species names
    cut -d , -f 2 $file | sort | uniq
done

Supongamos que acabamos de ejecutar una serie de comandos que han hecho algo útil, por ejemplo, crear un gráfico que nos gustaría utilizar en un artículo. Nos gustaría poder volver a crear el gráfico más tarde si lo necesitamos, así que queremos guardar los comandos en un archivo. En lugar de escribirlos de nuevo (y potencialmente equivocarnos) podemos hacer lo siguiente:

BASH

$ history | tail -n 5 > redo-figure-3.sh

El fichero redo-figure-3.sh contiene ahora:

297 bash goostats.sh NENE01729B.txt stats-NENE01729B.txt
298 bash goodiff.sh stats-NENE01729B.txt /data/validated/01729.txt > 01729-differences.txt
299 cut -d ',' -f 2-3 01729-differences.txt > 01729-time-series.txt
300 ygraph --format scatter --color bw --borders none 01729-time-series.txt figure-3.png
301 history | tail -n 5 > redo-figure-3.sh

Después de un momento de trabajo en un editor para eliminar los números de serie en los comandos, y para eliminar la línea final donde llamamos al comando history, tenemos un registro completamente exacto de cómo creamos esa figura.

¿Por qué registrar los comandos en el historial antes de ejecutarlos?

Si ejecutas el comando:

BASH

$ history | tail -n 5 > recent.sh

el último comando del archivo es el propio comando history, es decir, el shell ha añadido history al registro de comandos antes de ejecutarlo realmente. De hecho, el shell siempre añade comandos al registro antes de ejecutarlos. ¿Por qué crees que lo hace?

Si un comando hace que algo se bloquee o se cuelgue, puede ser útil saber cuál era ese comando para investigar el problema. Si el comando sólo se registrara después de ejecutarlo, no tendríamos un registro del último comando ejecutado en caso de que se produjera un bloqueo.

En la práctica, la mayoría de la gente desarrolla secuencias de comandos ejecutándolas unas cuantas veces para asegurarse de que están haciendo lo correcto y guardándolas en un archivo para poder reutilizarlas. Este estilo de trabajo permite a la gente reciclar lo que descubren sobre sus datos y su flujo de trabajo con una llamada a history y un poco de edición para limpiar la salida y guardarla como un script de shell.

Nelle’s Pipeline: Creación de un script


El supervisor de Nelle insistió en que todos sus análisis debían ser reproducibles. La forma más sencilla de capturar todos los pasos es en un script.

Primero volvemos al directorio del proyecto de Nelle:

BASH

$ cd ../../north-pacific-gyre/

Crea un fichero usando nano

BASH

$ nano do-stats.sh

…que contiene lo siguiente:

BASH

# Calculate stats for data files.
for datafile in "$@"
do
    echo $datafile
    bash goostats.sh $datafile stats-$datafile
done

Guarda esto en un archivo llamado do-stats.sh de modo que ahora puede volver a hacer la primera etapa de su análisis escribiendo:

BASH

$ bash do-stats.sh NENE*A.txt NENE*B.txt

También puede hacer esto:

BASH

$ bash do-stats.sh NENE*A.txt NENE*B.txt | wc -l

para que la salida sea sólo el número de archivos procesados en lugar de los nombres de los archivos que fueron procesados.

Una cosa a tener en cuenta sobre el script de Nelle es que deja que la persona que lo ejecuta decida qué archivos procesar. Podría haberlo escrito como:

BASH

# Calculate stats for Site A and Site B data files.
for datafile in NENE*A.txt NENE*B.txt
do
    echo $datafile
    bash goostats.sh $datafile stats-$datafile
done

La ventaja es que siempre selecciona los archivos correctos: no tiene que acordarse de excluir los archivos ‘Z’. La desventaja es que siempre selecciona sólo esos archivos — no puede ejecutarlo en todos los archivos (incluyendo los archivos ‘Z’), o en los archivos ‘G’ o ‘H’ que sus colegas en la Antártida están produciendo, sin editar el script. Si quisiera ser más atrevida, podría modificar el script para comprobar si hay argumentos en la línea de comandos y utilizar NENE*A.txt NENE*B.txt si no se proporciona ninguno. Por supuesto, esto introduce otro compromiso entre flexibilidad y complejidad.

Variables en Shell Scripts

En el directorio alkanes, imagine que tiene un script de shell llamado script.sh que contiene los siguientes comandos:

BASH

head -n $2 $1
tail -n $3 $1

Mientras estás en el directorio alkanes, escribe el siguiente comando:

BASH

$ bash script.sh '*.pdb' 1 1

¿Cuál de los siguientes resultados esperaría ver?

  1. Todas las líneas entre la primera y la última línea de cada fichero que termina en .pdb en el directorio alkanes
  2. La primera y la última línea de cada fichero que termina en .pdb en el directorio alkanes
  3. La primera y la última línea de cada fichero en el directorio alkanes
  4. Error debido a las comillas alrededor de *.pdb

La respuesta correcta es 2.

Las variables especiales $1, $2 y $3 representan los argumentos de línea de comandos dados al script, de tal forma que los comandos ejecutados son:

BASH

$ head -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb
$ tail -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb

El shell no expande '*.pdb' porque está entre comillas. Como tal, el primer argumento del script es '*.pdb' que se expande dentro del script por head y tail.

Encuentra el fichero más largo con una extensión dada

Escribe un script de shell llamado longest.sh que tome como argumentos el nombre de un directorio y una extensión de nombre de archivo, e imprima el nombre del archivo con más líneas en ese directorio con esa extensión. Por ejemplo:

BASH

$ bash longest.sh shell-lesson-data/exercise-data/alkanes pdb

imprimiría el nombre del fichero .pdb en shell-lesson-data/exercise-data/alkanes que tiene más líneas.

Siéntete libre de probar tu script en otro directorio p.e.

BASH

$ bash longest.sh shell-lesson-data/exercise-data/writing txt

BASH

# Shell script which takes two arguments:
#    1. a directory name
#    2. a file extension
# and prints the name of the file in that directory
# with the most lines which matches the file extension.

wc -l $1/*.$2 | sort -n | tail -n 2 | head -n 1

La primera parte del proceso, wc -l $1/*.$2 | sort -n, cuenta las líneas de cada archivo y las ordena numéricamente (la más grande en último lugar). Cuando hay más de un archivo, wc también genera una línea de resumen final, dando el número total de líneas en todos los archivos. Usamos tail -n 2 | head -n 1 para desechar esta última línea.

Con wc -l $1/*.$2 | sort -n | tail -n 1 veremos la línea de resumen final: podemos construir nuestro pipeline por partes para estar seguros de que entendemos la salida.

Comprensión de lectura del script

Para esta pregunta, consideremos de nuevo el directorio shell-lesson-data/exercise-data/alkanes. Este contiene un número de archivos .pdb además de cualquier otro archivo que pueda haber creado. Explique qué haría cada uno de los tres scripts siguientes al ejecutarse como bash script1.sh *.pdb, bash script2.sh *.pdb y bash script3.sh *.pdb respectivamente.

BASH

# Script 1
echo *.*

BASH

# Script 2
for filename in $1 $2 $3
do
    cat $filename
done

BASH

# Script 3
echo $@.pdb

En cada caso, el shell expande el comodín en *.pdb antes de pasar la lista resultante de nombres de archivo como argumentos al script.

El script 1 imprimiría una lista de todos los archivos que contienen un punto en su nombre. Los argumentos pasados al script no se utilizan realmente en ninguna parte del script.

El script 2 imprimiría el contenido de los 3 primeros ficheros con extensión .pdb. los argumentos $1, $2 y $3 se refieren al primer, segundo y tercer argumento respectivamente.

El script 3 imprimiría todos los argumentos del script (es decir, todos los archivos .pdb), seguidos de .pdb.$@ se refiere a todos los argumentos dados a un script de shell.

SALIDA

cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb

Scripts de depuración

Suponga que ha guardado el siguiente script en un archivo llamado do-errors.sh en el directorio north-pacific-gyre de Nelle:

BASH

# Calculate stats for data files.
for datafile in "$@"
do
    echo $datfile
    bash goostats.sh $datafile stats-$datafile
done

Cuando lo ejecutas desde el directorio north-pacific-gyre:

BASH

$ bash do-errors.sh NENE*A.txt NENE*B.txt

la salida está en blanco. Para averiguar por qué, vuelva a ejecutar el script utilizando la opción -x:

BASH

$ bash -x do-errors.sh NENE*A.txt NENE*B.txt

¿Qué te muestra la salida? ¿Qué línea es la responsable del error?

La opción -x hace que bash se ejecute en modo de depuración. Esto imprime cada comando a medida que se ejecuta, lo que le ayudará a localizar errores. En este ejemplo, podemos ver que echo no imprime nada. Hemos cometido un error tipográfico en el nombre de la variable de bucle, y la variable datfile no existe, por lo que devuelve una cadena vacía.

Puntos Clave

  • Guarda comandos en archivos (normalmente llamados shell scripts) para reutilizarlos.
  • bash [filename] ejecuta los comandos guardados en un archivo.
  • $@ se refiere a todos los argumentos de línea de comandos de un script de shell.
  • $1, $2, etc., se refieren al primer argumento de la línea de comandos, al segundo argumento de la línea de comandos, etc.
  • Ponga las variables entre comillas si los valores pueden contener espacios.
  • Dejar que los usuarios decidan qué archivos procesar es más flexible y más consistente con los comandos Unix incorporados.

Content from Encontrar cosas


Última actualización: 2025-04-03 | Mejora esta página

Hoja de ruta

Preguntas

  • ¿Cómo puedo encontrar los archivos?
  • ¿Cómo puedo encontrar cosas en archivos?

Objetivos

  • Utilice grep para seleccionar líneas de archivos de texto que coincidan con patrones simples.
  • Utilice find para buscar archivos y directorios cuyos nombres coincidan con patrones sencillos.
  • Utilizar la salida de un comando como argumento(s) de otro comando.
  • Explique qué se entiende por ficheros ‘de texto’ y ‘binarios’, y por qué muchas herramientas comunes no manejan bien estos últimos.

Del mismo modo que muchos de nosotros utilizamos ahora “Google” como verbo que significa “encontrar”, los programadores de Unix utilizan a menudo la palabra “grep”. grep” es una contracción de “global/regular expression/print”, una secuencia de operaciones habitual en los primeros editores de texto de Unix. También es el nombre de un programa de línea de comandos muy útil.

grep encuentra e imprime las líneas de los archivos que coinciden con un patrón. Para nuestros ejemplos, utilizaremos un archivo que contiene tres haiku tomados de un concurso de 1998 en la revista Salon (Crédito a los autores Bill Torcaso, Howard Korder, y Margaret Segall, respectivamente. Véase Haiku Error Messsages archivado Página 1 y Página 2 .). Para este conjunto de ejemplos, vamos a trabajar en el subdirectorio de escritura:

BASH

$ cd
$ cd Desktop/shell-lesson-data/exercise-data/writing
$ cat haiku.txt

SALIDA

The Tao that is seen
Is not the true Tao, until
You bring fresh toner.

With searching comes loss
and the presence of absence:
"My Thesis" not found.

Yesterday it worked
Today it is not working
Software is like that.

Busquemos líneas que contengan la palabra “no”:

BASH

$ grep not haiku.txt

SALIDA

Is not the true Tao, until
"My Thesis" not found
Today it is not working

Aquí, not es el patrón que estamos buscando. El comando grep busca en el fichero las coincidencias con el patrón especificado. Para utilizarlo escriba grep, luego el patrón que estamos buscando y finalmente el nombre del archivo (o archivos) en el que estamos buscando.

La salida son las tres líneas del fichero que contienen las letras “no”.

Por defecto, grep busca un patrón distinguiendo entre mayúsculas y minúsculas. Además, el patrón de búsqueda que hemos seleccionado no tiene por qué formar una palabra completa, como veremos en el siguiente ejemplo.

Busquemos el patrón: ‘The’.

BASH

$ grep The haiku.txt

SALIDA

The Tao that is seen
"My Thesis" not found.

Esta vez se muestran dos líneas que incluyen las letras “The”, una de las cuales contiene nuestro patrón de búsqueda dentro de una palabra más grande, “Thesis”.

Para restringir las coincidencias a las líneas que contengan la palabra ‘El’ sola, podemos dar a grep la opción -w. Esto limitará las coincidencias a los límites de las palabras.

Más adelante en esta lección, también veremos cómo podemos cambiar el comportamiento de búsqueda de grep con respecto a su sensibilidad a mayúsculas y minúsculas.

BASH

$ grep -w The haiku.txt

SALIDA

The Tao that is seen

Tenga en cuenta que un “límite de palabra” incluye el principio y el final de una línea, es decir, no sólo las letras rodeadas de espacios. A veces no queremos buscar una sola palabra, sino una frase. También podemos hacerlo con grep poniendo la frase entre comillas.

BASH

$ grep -w "is not" haiku.txt

SALIDA

Today it is not working

Ya hemos visto que no es necesario entrecomillar las palabras sueltas, pero es útil utilizar comillas cuando se buscan varias palabras. También ayuda a distinguir más fácilmente entre el término o frase de búsqueda y el fichero buscado. Utilizaremos comillas en los ejemplos siguientes.

Otra opción útil es -n, que numera las líneas que coinciden:

BASH

$ grep -n "it" haiku.txt

SALIDA

5:With searching comes loss
9:Yesterday it worked
10:Today it is not working

Aquí podemos ver que las líneas 5, 9 y 10 contienen las letras “it”.

Podemos combinar opciones (es decir, banderas) como hacemos con otros comandos Unix. Por ejemplo, busquemos las líneas que contienen la palabra ‘the’. Podemos combinar la opción -w para encontrar las líneas que contienen la palabra ‘the’ y -n para numerar las líneas que coinciden:

BASH

$ grep -n -w "the" haiku.txt

SALIDA

2:Is not the true Tao, until
6:and the presence of absence:

Ahora queremos utilizar la opción -i para que nuestra búsqueda no distinga entre mayúsculas y minúsculas:

BASH

$ grep -n -w -i "the" haiku.txt

SALIDA

1:The Tao that is seen
2:Is not the true Tao, until
6:and the presence of absence:

Ahora, queremos utilizar la opción -v para invertir nuestra búsqueda, es decir, queremos que aparezcan las líneas que no contienen la palabra ‘the’.

BASH

$ grep -n -w -v "the" haiku.txt

SALIDA

1:The Tao that is seen
3:You bring fresh toner.
4:
5:With searching comes loss
7:"My Thesis" not found.
8:
9:Yesterday it worked
10:Today it is not working
11:Software is like that.

Si utilizamos la opción -r (recursiva), grep puede buscar un patrón recursivamente a través de un conjunto de ficheros en subdirectorios.

Busquemos recursivamente Yesterday en el directorio shell-lesson-data/exercise-data/writing:

BASH

$ grep -r Yesterday .

SALIDA

./LittleWomen.txt:"Yesterday, when Aunt was asleep and I was trying to be as still as a
./LittleWomen.txt:Yesterday at dinner, when an Austrian officer stared at us and then
./LittleWomen.txt:Yesterday was a quiet day spent in teaching, sewing, and writing in my
./haiku.txt:Yesterday it worked

grep tiene muchas otras opciones. Para saber cuáles son, podemos teclear:

BASH

$ grep --help

SALIDA

Usage: grep [OPTION]... PATTERN [FILE]...
Search for PATTERN in each FILE or standard input.
PATTERN is, by default, a basic regular expression (BRE).
Example: grep -i 'hello world' menu.h main.c

Regexp selection and interpretation:
  -E, --extended-regexp     PATTERN is an extended regular expression (ERE)
  -F, --fixed-strings       PATTERN is a set of newline-separated fixed strings
  -G, --basic-regexp        PATTERN is a basic regular expression (BRE)
  -P, --perl-regexp         PATTERN is a Perl regular expression
  -e, --regexp=PATTERN      use PATTERN for matching
  -f, --file=FILE           obtain PATTERN from FILE
  -i, --ignore-case         ignore case distinctions
  -w, --word-regexp         force PATTERN to match only whole words
  -x, --line-regexp         force PATTERN to match only whole lines
  -z, --null-data           a data line ends in 0 byte, not newline

Miscellaneous:
...        ...        ...

Usando grep

¿Qué comando daría como resultado la siguiente salida:

SALIDA

and the presence of absence:
  1. grep "of" haiku.txt
  2. grep -E "of" haiku.txt
  3. grep -w "of" haiku.txt
  4. grep -i "of" haiku.txt

La respuesta correcta es 3, porque la opción -w sólo busca coincidencias de palabras completas. Las otras opciones también coincidirán con “de” cuando forme parte de otra palabra.

Comodines

El verdadero poder de grep no proviene de sus opciones, sino del hecho de que los patrones pueden incluir comodines. (El nombre técnico para éstos es expresiones regulares, que es lo que significa la “re” en “grep”) Las expresiones regulares son complejas y potentes; si quieres hacer búsquedas complejas, consulta la lección en nuestro sitio web. Como muestra, podemos encontrar líneas que tengan una ‘o’ en la segunda posición de la siguiente manera:

BASH

$ grep -E "^.o" haiku.txt

SALIDA

You bring fresh toner.
Today it is not working
Software is like that.

Usamos la opción -E y ponemos el patrón entre comillas para evitar que el shell intente interpretarlo. (Si el patrón contuviera un *, por ejemplo, el shell intentaría expandirlo antes de ejecutar grep) El ^ en el patrón ancla la coincidencia al principio de la línea. El . coincide con un único carácter (igual que ? en el shell), mientras que el o coincide con una o real.

Seguimiento de una especie

Leah tiene varios cientos de archivos de datos guardados en un directorio, cada uno de los cuales tiene el siguiente formato:

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-06,fox,4
2012-11-07,rabbit,16
2012-11-07,bear,1

Quiere escribir un script de shell que tome una especie como primer argumento de la línea de comandos y un directorio como segundo argumento. El script debe devolver un fichero llamado <species>.txt que contenga una lista de fechas y el número de esa especie visto en cada fecha. Por ejemplo, utilizando los datos mostrados anteriormente, rabbit.txt contendría:

2012-11-05,22
2012-11-06,19
2012-11-07,16

A continuación, cada línea contiene un comando individual, o tubería. Ordene su secuencia en un comando para lograr el objetivo de Leah:

BASH

cut -d : -f 2
>
|
grep -w $1 -r $2
|
$1.txt
cut -d , -f 1,3

Sugerencia: use man grep para buscar como grep texto recursivamente en un directorio y man cut para seleccionar más de un campo en una línea.

En shell-lesson-data/exercise-data/animal-counts/animals.csv se proporciona un ejemplo de un archivo de este tipo

grep -w $1 -r $2 | cut -d : -f 2 | cut -d , -f 1,3 > $1.txt

En realidad, se puede cambiar el orden de los dos comandos de corte y todavía funciona. En la línea de comandos, intente cambiar el orden de los comandos de corte, y eche un vistazo a la salida de cada paso para ver por qué este es el caso.

Llamarías al script de arriba así:

BASH

$ bash count-species.sh bear .

Mujercitas

Tú y tu amiga, que acabáis de terminar de leer Mujercitas de Louisa May Alcott, estáis discutiendo. De las cuatro hermanas del libro, Jo, Meg, Beth y Amy, tu amiga piensa que Jo era la más mencionada. Tú, sin embargo, estás segura de que fue Amy. Por suerte, tienes un fichero LittleWomen.txt que contiene el texto completo de la novela (shell-lesson-data/exercise-data/writing/LittleWomen.txt). Utilizando un bucle for, ¿cómo tabularías el número de veces que se menciona a cada una de las cuatro hermanas?

Sugerencia: una solución puede emplear los comandos grep y wc y un |, mientras que otra puede utilizar las opciones grep. A menudo hay más de una manera de resolver una tarea de programación, por lo que una solución particular se elige generalmente sobre la base de una combinación de producir el resultado correcto, la elegancia, la legibilidad y la velocidad.

for sis in Jo Meg Beth Amy
do
    echo $sis:
    grep -ow $sis LittleWomen.txt | wc -l
done

Solución alternativa, ligeramente inferior:

for sis in Jo Meg Beth Amy
do
    echo $sis:
    grep -ocw $sis LittleWomen.txt
done

Esta solución es inferior porque grep -c sólo informa del número de líneas coincidentes. El número total de coincidencias informado por este método será menor si hay más de una coincidencia por línea.

Los observadores más perspicaces se habrán dado cuenta de que los nombres de los personajes a veces aparecen en mayúsculas en los títulos de los capítulos (por ejemplo, “MEG GOES TO VANITY FAIR”). Si quiere contarlos también, puede añadir la opción -i para no distinguir entre mayúsculas y minúsculas (aunque en este caso, no afecta a la respuesta sobre qué hermana se menciona con más frecuencia).

Mientras que grep busca líneas en ficheros, el comando find busca ficheros en sí. De nuevo, tiene muchas opciones; para mostrar cómo funcionan las más simples, usaremos el árbol de directorios shell-lesson-data/exercise-data que se muestra a continuación.

SALIDA

.
├── animal-counts/
│   └── animals.csv
├── creatures/
│   ├── basilisk.dat
│   ├── minotaur.dat
│   └── unicorn.dat
├── numbers.txt
├── alkanes/
│   ├── cubane.pdb
│   ├── ethane.pdb
│   ├── methane.pdb
│   ├── octane.pdb
│   ├── pentane.pdb
│   └── propane.pdb
└── writing/
    ├── haiku.txt
    └── LittleWomen.txt

El directorio exercise-data contiene un fichero, numbers.txt y cuatro directorios: animal-counts, creatures, alkanes y writing que contienen varios ficheros.

Para nuestro primer comando, vamos a ejecutar find . (recuerde ejecutar este comando desde la carpeta shell-lesson-data/exercise-data).

BASH

$ find .

SALIDA

.
./writing
./writing/LittleWomen.txt
./writing/haiku.txt
./creatures
./creatures/basilisk.dat
./creatures/unicorn.dat
./creatures/minotaur.dat
./animal-counts
./animal-counts/animals.csv
./numbers.txt
./alkanes
./alkanes/ethane.pdb
./alkanes/propane.pdb
./alkanes/octane.pdb
./alkanes/pentane.pdb
./alkanes/methane.pdb
./alkanes/cubane.pdb

Como siempre, . por sí solo significa el directorio de trabajo actual, que es donde queremos que empiece nuestra búsqueda. la salida de find son los nombres de cada archivo y directorio bajo el directorio de trabajo actual. Esto puede parecer inútil al principio, pero find tiene muchas opciones para filtrar la salida y en esta lección descubriremos algunas de ellas.

La primera opción de nuestra lista es -type d que significa ‘cosas que son directorios’. Efectivamente, la salida de find son los nombres de los cinco directorios (incluyendo .):

BASH

$ find . -type d

SALIDA

.
./writing
./creatures
./animal-counts
./alkanes

Observe que los objetos encontrados por find no están ordenados. Si cambiamos -type d por -type f, obtendremos un listado de todos los ficheros:

BASH

$ find . -type f

SALIDA

./writing/LittleWomen.txt
./writing/haiku.txt
./creatures/basilisk.dat
./creatures/unicorn.dat
./creatures/minotaur.dat
./animal-counts/animals.csv
./numbers.txt
./alkanes/ethane.pdb
./alkanes/propane.pdb
./alkanes/octane.pdb
./alkanes/pentane.pdb
./alkanes/methane.pdb
./alkanes/cubane.pdb

Ahora probemos a buscar por nombre:

BASH

$ find . -name *.txt

SALIDA

./numbers.txt

Esperábamos que encontrara todos los archivos de texto, pero sólo imprime ./numbers.txt. El problema es que el shell expande caracteres comodín como * antes de que se ejecuten los comandos. Como *.txt en el directorio actual se expande a ./numbers.txt, el comando que realmente ejecutamos fue:

BASH

$ find . -name numbers.txt

find hizo lo que pedimos; sólo que pedimos la cosa equivocada.

Para conseguir lo que queremos, hagamos lo que hicimos con grep: pongamos *.txt entre comillas para evitar que el shell expanda el comodín *. De esta forma, find obtiene el patrón *.txt, no el nombre de fichero expandido numbers.txt:

BASH

$ find . -name "*.txt"

SALIDA

./writing/LittleWomen.txt
./writing/haiku.txt
./numbers.txt

Listado vs. Búsqueda

Se puede hacer que ls y find hagan cosas similares dadas las opciones adecuadas, pero en circunstancias normales, ls lista todo lo que puede, mientras que find busca cosas con ciertas propiedades y las muestra.

Como hemos dicho antes, el poder de la línea de comandos reside en la combinación de herramientas. Hemos visto cómo hacerlo con tuberías; veamos otra técnica. Como acabamos de ver, find . -name "*.txt" nos da una lista de todos los archivos de texto dentro o debajo del directorio actual. ¿Cómo podemos combinar eso con wc -l para contar las líneas en todos esos ficheros?

La forma más sencilla es poner el comando find dentro de $():

BASH

$ wc -l $(find . -name "*.txt")

SALIDA

  21022 ./writing/LittleWomen.txt
     11 ./writing/haiku.txt
      5 ./numbers.txt
  21038 total

Cuando el shell ejecuta este comando, lo primero que hace es ejecutar lo que hay dentro de $(). Luego reemplaza la expresión $() con la salida de ese comando. Como la salida de find son los tres nombres de fichero ./writing/LittleWomen.txt, ./writing/haiku.txt, y ./numbers.txt, el shell construye el comando:

BASH

$ wc -l ./writing/LittleWomen.txt ./writing/haiku.txt ./numbers.txt

que es lo que queríamos. Esta expansión es exactamente lo que hace el shell cuando expande comodines como * y ?, pero nos permite usar cualquier comando que queramos como nuestro propio ‘comodín’.

Es muy común utilizar find y grep a la vez. El primero busca ficheros que coincidan con un patrón; el segundo busca líneas dentro de esos ficheros que coincidan con otro patrón. Aquí, por ejemplo, podemos encontrar archivos txt que contengan la palabra “searching” buscando la cadena ‘searching’ en todos los archivos .txt del directorio actual:

BASH

$ grep "searching" $(find . -name "*.txt")

SALIDA

./writing/LittleWomen.txt:sitting on the top step, affected to be searching for her book, but was
./writing/haiku.txt:With searching comes loss

Correspondencia y sustracción

La opción -v de grep invierte la coincidencia de patrones, de modo que sólo se imprimen las líneas que no coinciden con el patrón. Teniendo esto en cuenta, ¿cuál de los siguientes comandos encontrará todos los ficheros .dat en creatures excepto unicorn.dat? Una vez hayas pensado tu respuesta, puedes probar los comandos en el directorio shell-lesson-data/exercise-data.

  1. find creatures -name "*.dat" | grep -v unicorn
  2. find creatures -name *.dat | grep -v unicorn
  3. grep -v "unicorn" $(find creatures -name "*.dat")
  4. Ninguna de las anteriores.

La opción 1 es correcta. Al poner la expresión de coincidencia entre comillas se evita que el shell la expanda, por lo que se pasa al comando find.

La opción 2 también funciona en este caso porque el shell intenta expandir *.dat pero no hay ficheros *.dat en el directorio actual, así que la expresión comodín se pasa a find. Encontramos esto por primera vez en el episodio 3.

La opción 3 es incorrecta porque busca en el contenido de los ficheros las líneas que no coinciden con “unicornio”, en lugar de buscar en los nombres de los ficheros.

Archivos binarios

Nos hemos centrado exclusivamente en la búsqueda de patrones en archivos de texto. ¿Qué ocurre si sus datos están almacenados en imágenes, en bases de datos o en algún otro formato?

Un puñado de herramientas extienden grep para manejar algunos formatos que no son de texto. Pero un enfoque más generalizable es convertir los datos en texto, o extraer los elementos similares al texto de los datos. Por un lado, facilita las cosas sencillas. Por otro, las cosas complejas suelen ser imposibles. Por ejemplo, es bastante fácil escribir un programa que extraiga las dimensiones X e Y de archivos de imagen para que grep juegue con ellas, pero ¿cómo escribir algo para encontrar valores en una hoja de cálculo cuyas celdas contengan fórmulas?

Una última opción es reconocer que el shell y el procesamiento de texto tienen sus límites, y utilizar otro lenguaje de programación. Cuando llegue el momento de hacerlo, no seas demasiado duro con el shell. Muchos lenguajes de programación modernos han tomado prestadas muchas ideas de él, y la imitación es también la forma más sincera de elogio.

El shell de Unix es más antiguo que la mayoría de las personas que lo utilizan. Ha sobrevivido tanto tiempo porque es uno de los entornos de programación más productivos jamás creados — quizás incluso el más productivo. Su sintaxis puede ser críptica, pero la gente que la domina puede experimentar con diferentes comandos de forma interactiva, y luego utilizar lo que han aprendido para automatizar su trabajo. Las interfaces gráficas de usuario pueden ser más fáciles de usar al principio, pero una vez aprendidas, la productividad del shell es insuperable. Y como escribió Alfred North Whitehead en 1911, ‘La civilización avanza ampliando el número de operaciones importantes que podemos realizar sin pensar en ellas’

find Comprensión de lectura de tuberías

Escriba un breve comentario explicativo para el siguiente script de shell:

BASH

wc -l $(find . -name "*.dat") | sort -n
  1. Buscar todos los ficheros con extensión .dat recursivamente desde el directorio actual
  2. Cuente el número de líneas que contiene cada uno de estos ficheros
  3. Ordena numéricamente la salida del paso 2

Puntos Clave

  • find encuentra archivos con propiedades específicas que coinciden con los patrones.
  • grep selecciona las líneas de los archivos que coinciden con los patrones.
  • --help es una opción soportada por muchos comandos bash, y programas que pueden ser ejecutados desde dentro de Bash, para mostrar más información sobre cómo usar estos comandos o programas.
  • man [command] muestra la página del manual de un comando determinado.
  • $([command]) inserta la salida de un comando en su lugar.