El ámbito de un comando sirve para especificar las líneas del flujo de entrada en las que se ejecutará, indica una condición que tiene que cumplir la línea para que actúe el comando sobre ella. Es opcional, y si no se pone, el comando se ejecutará para todas las líneas.
Hay dos formas de expresar la condición que tiene que cumplir la línea, es decir, el ámbito:
- Mediante un número, con el cual se está diciendo que el número de la línea del flujo de entrada debe coincidir con dicho número para la ejecución del comando.
- Mediante una expresión regular, con la cual se está diciendo que la línea debe contener texto que case con la expresión para que el comando se ejecute.
Apliquemos el comando s solo a la línea 4 del flujo de entrada:
$ cat f
uno
dos
tres
$ cat g
cuatro
$ sed '4s/^/-/' f g
uno
dos
tres
-cuatro
Recordemos que en principio si no cambiamos nada, el flujo en entrada es único y surge como la concatenación de todos los ficheros que se indiquen como argumentos, en este caso, la línea número 4 del flujo de entrada se corresponde con la primera línea del fichero g.
Ya vimos que la opción -i hace que sed trabaje individualmente con los ficheros y no se cree el flujo de entrada único. En este caso, las líneas de cada fichero se numeran a partir de la número 1. Veámoslo en el siguiente ejemplo:
$ sed -i '1s/^/-/' f g
$ cat f
-uno
dos
tres
$ cat g
-cuatro
Opción -s, --separate
La opción -s fuerza a sed a trabajar individualmente con los ficheros, por lo que no crea un flujo de entrada único. La opción -i que ya hemos visto, asume la opción -s.
Podemos repetir el comando s anterior, pero esta vez usando la opción --separate:
$ sed -s '1s/^/-/' f g
-uno
dos
tres
-cuatro
Como ámbito numérico se puede usar el carácter $ para referirnos a la última línea del flujo de entrada:
$ sed '$s/^/-/' f g
uno
dos
tres
-cuatro
$ sed -s '$s/^/-/' f g
uno
dos
-tres
-cuatro
Se puede indicar como ámbito el complementario del especificado, para lo cual hay que finalizar el ámbito con el carácter !. Veámoslo en el siguiente ejemplo:
$ sed '1s/^/-/' f g
-uno
dos
tres
cuatro
$ sed '1!s/^/-/' f g
uno
-dos
-tres
-cuatro
Como vemos 1! hace referencia a todas las líneas menos a la 1.
Como extensión GNU, otra posibilidad de la que disponemos con un ámbito numérico es la de referirnos a un grupo de líneas equidistantes, la sintaxis que debemos usar es inicio~distancia, veámoslo con un ejemplo para referirnos a las líneas impares:
$ sed '1~2s/^/-/' f g
-uno
dos
-tres
cuatro
Para las líneas pares sería:
$ sed '2~2s/^/-/' f g
uno
-dos
tres
-cuatro
Esto último, también lo podríamos haber hecho usando la pseudolínea 0, línea que está antes de la 1 pero que no existe, por eso no se ve afectada por el comando s:
$ sed '0~2s/^/-/' f g
uno
-dos
tres
-cuatro
La línea 0 tiene otros usos más interesantes que la hacen necesaria y lo veremos más adelante.
Como se dijo anteriormente, la otra posibilidad de expresar ámbitos es mediante las expresiones regulares, veamos el siguiente ejemplo, donde el comando s solo actuará sobre las líneas que contengan la letra u:
$ sed '/u/s/^/-/' f g
-uno
dos
tres
-cuatro
Como vemos, la expresión regular del ámbito se delimita por el carácter /, de modo que si dicho carácter estuviera dentro de la expresión regular, deberíamos protegerlo con el carácter \, habría que poner \/, pero también es posible cambiar el delimitador del ámbito, para ello usaremos la siguiente sintaxis:
\Xexpresión_regularX
donde X puede ser cualquier carácter, lo normal será elegir uno que no esté dentro de la expresión regular, así, el mismo ejemplo anterior es válido también como se escribe a continuación:
$ sed '\$u$s/^/-/' f g
-uno
dos
tres
-cuatro
Por defecto, las expresiones regulares del ámbito son sensibles a mayúsculas y minúsculas, pero podemos hacer que no lo sean si usamos el modificador I. Los modificadores se ponen después del ámbito. Veamos su efecto:
$ sed '/U/s/^/-/' f g
uno
dos
tres
cuatro
$ sed '/U/Is/^/-/' f g
-uno
dos
tres
-cuatro
El modificador I no es estándar, es una extensión de GNU.
Con la expresión regular también se puede complementar el ámbito, usemos el mismo ejemplo anterior, pero al contrario, aplicamos el comando s a todas las líneas menos a las que contengan la vocal 'u', independiente de mayúsculas y minúsculas:
$ sed '/U/I!s/^/-/' f g
uno
-dos
-tres
cuatro
Por último, vamos a ver la posibilidad de especificar un ámbito que constituya un rango de líneas. Para ello debemos expresar desde dónde comienza el ámbito y dónde termina, con el punto final incluido. Estos valores pueden ser un número o una expresión regular, como hasta ahora, y se separarán por una coma flanqueada o no por espacios.
inicio,fin
Veamos las distintas formas de escribir un ámbitos como rango de líneas. Si ponemos dos números, estarémos diciendo que el ámbito del comando va desde la primera línea especificada hasta la última dada, esta incluida. Por ejemplo, desde la línea 2 hasta la línea 3:
$ sed '2,3s/^/-/' f g
uno
-dos
-tres
cuatro
Desde la línea 2 hasta la última del flujo de entrada:
$ sed '2,$s/^/-/' f g
uno
-dos
-tres
-cuatro
Si el rango se escribe al revés, por ejemplo, indicamos 3,2, no se produce un error, sino que no hay rango, nos estaremos refiriendo solo a una línea, la indicada por inicio, la línea 3 en el ejemplo:
$ sed '3,2s/^/-/' f g
uno
dos
-tres
cuatro
Es posible escribir un rango así: 10,+4, y estaríamos diciendo desde la 10 más las 4 líneas siguientes, sería equivalente a 10,14. Y es posible también escribir esto: 10,~4, donde el ámbito sería desde la línea 10 hasta la primera línea después de la 10 que sea múltiplo de 4, y aquí también, podríamos nosotros hacer el cálculo y especificar 10,12. Estas dos sintaxis son exclusivas de GNU y son más útiles con las expresiones regulares.
$ sed '1,+2s/^/-/' f g
-uno
-dos
-tres
cuatro
$ sed '1,~2s/^/-/' f g
-uno
-dos
tres
cuatro
Podemos poner también como rango un número de inicio y una expresión regular como final. Estaríamos especificando un rango que va desde el número de línea indicado, hasta la primera línea que contenga la expresión regular a partir de la siguiente a la inicial. En la línea inicial no se comprueba la expresión regular que indica el final. Si no hubiera ninguna línea que concuerde, la última línea será la última del flujo de entrada.
El siguiente ejemplo empieza en la línea 1 y termina en la primera línea que contenga una 'o' a partir de la siguiente a la primera del rango:
$ sed '1,/o/s/^/-/' f g
-uno
-dos
tres
cuatro
$ sed '1,/x/s/^/-/' f g
-uno
-dos
-tres
-cuatro
Es aquí donde la pseudolínea 0 (extensión de GNU) se hace necesaria, pues si quisieramos ejecutar un comando solo desde el principio del flujo de entrada hasta la primera línea que contenga una 'o', incluida la primera línea en el posible final, no podemos hacerlo como en el ejemplo anterior, pues debería haber actuado solo sobre "uno", la primera línea.
Tampoco se puede hacer así, pues aplicaríamos el comando a todas las líneas donde haya una 'o':
$ sed '/o/s/^/-/' f g
-uno
-dos
tres
-cuatro
Necesitaríamos indicar la pseudolínea 0, que no existe, pero hace que la expresión regular de final del ámbito pueda aplicarse a la línea 1:
$ sed '0,/o/s/^/-/' f g
-uno
dos
tres
cuatro
Cuando el rango del ámbito empieza por una expresión regular, no estamos creando un solo rango, sino múltiples rangos disjuntos, el primer rango iría desde la primera línea que contenga la expresión regular hasta el final indicado por el segundo valor del rango, y a partir de ahí, se vuelve a definir otro rango, que comenzaría en la primera línea después del primer rango que contenga la expresión regular y hasta el final indicado por el segundo valor del rango, y así sucesivamente hasta el final del flujo de entrada. Veamos esto con ejemplos.
/expresión_regular/,numero
/0/,3
Crearíamos sucesivos rangos que comienzan en líneas que contengan el carácter 0 y terminan en la línea 3, pero como la línea 3 es única, a partir de ella los rangos solo son de una línea, la que contenga la expresión regular, pues todas las líneas ya son superiores a 3:
$ seq 0 20 | sed '/0/,3s/^/-/'
-0
-1
-2
3
4
5
6
7
8
9
-10
11
12
13
14
15
16
17
18
19
-20
En expresiones como la anterior es frecuente usar como número de linea final el $, la última línea del flujo de entrada. Estaríamos definiendo un rango desde la primera línea que contenga una expresión regular hasta el final del flujo de entrada.
$ seq 15 | sed '/0/,$s/^/-/'
1
2
3
4
5
6
7
8
9
-10
-11
-12
-13
-14
-15
Otro formato que podemos utilizar es el siguiente:
/expresión_regular/,+numero
/0/,+3
Definimos múltiples rangos disjuntos que empiezan en una línea que contenga el carácter 0 más las tres siguientes líneas:
$ seq 0 20 | sed '/0/,+3s/^/-/'
-0
-1
-2
-3
4
5
6
7
8
9
-10
-11
-12
-13
14
15
16
17
18
19
-20
Ojo, si en alguna de las tres siguientes líneas, hubiera un 0, no se tendría en cuenta para crear un nuevo rango, los rangos son disjuntos y se busca el siguiente a partir del final del anterior.
Veamos el formato que hace referencia a líneas múltiplo de un número:
/expresión_regular/,~numero
/0/,~3
Definimos múltiples rangos disjuntos que empiezan en una línea que contenga el carácter 0 hasta la siguiente línea que sea múltiplo de 3:
$ seq 0 20 | sed '/0/,~3s/^/-/'
-0
-1
-2
3
4
5
6
7
8
9
-10
-11
12
13
14
15
16
17
18
19
-20
Por último, tenemos la posibilidad de usar dos expresiones regulares:
/expresión_regular/,/expresión_regular/
/0/,/3/
Creamos múltiples rangos disjuntos que empiezan en una línea que contenga el carácter 0 hasta la siguiente línea que contenga el carácter 3:
$ seq 0 20 | sed '/0/,/3/s/^/-/'
-0
-1
-2
-3
4
5
6
7
8
9
-10
-11
-12
-13
14
15
16
17
18
19
-20
Lo contrario al anterior ejemplo:
$ seq 0 20 | sed '/0/,/3/!s/^/-/'
0
1
2
3
-4
-5
-6
-7
-8
-9
10
11
12
13
-14
-15
-16
-17
-18
-19
20
Hasta ahora, se ha aplicado un ámbito a un único comando, si tuvieramos que aplicar el mismo ámbito a varios comandos, con lo que hemos visto, repetiríamos el ámbito en todos los comandos. Pues bién, existe otra posibilidad más cómoda, que consiste en agrupar varios comandos para que constituyan uno solo, y aplicar el ámbito al agrupamiento, de esta forma afectará a todos los comandos del agrupamiento.
Sintaxis del agrupamiento en una sola línea:
{ comando1 ; comando2 ; ... ; comandoN }
Y en varias líneas:
{
comando1
comando2
...
comandoN
}
O una mezcla:
{
comando1 ; comando2 ;
comando3
...
comandoN
}
Por ejemplo, el siguiente comando:
$ seq 10 | sed -n '/6/,$s/^/-/ ; /6/,$p'
-6
-7
-8
-9
-10
podría ejecutarse mejor así, si usamos el agrupamiento:
$ seq 10 | sed -n '/6/,${ s/^/-/ ; p }'
-6
-7
-8
-9
-10