¿Cómo pelar múltiples espacios a uno usando sed?

69

seden AIX no está haciendo lo que creo que debería. Estoy tratando de reemplazar múltiples espacios con un solo espacio en la salida de IOSTAT:

# iostat
System configuration: lcpu=4 drives=8 paths=2 vdisks=0

tty:      tin         tout    avg-cpu: % user % sys % idle % iowait
          0.2         31.8                9.7   4.9   82.9      2.5

Disks:        % tm_act     Kbps      tps    Kb_read   Kb_wrtn
hdisk9           0.2      54.2       1.1   1073456960  436765896
hdisk7           0.2      54.1       1.1   1070600212  435678280
hdisk8           0.0       0.0       0.0          0         0
hdisk6           0.0       0.0       0.0          0         0
hdisk1           0.1       6.3       0.5   63344916  112429672
hdisk0           0.1       5.0       0.2   40967838  98574444
cd0              0.0       0.0       0.0          0         0
hdiskpower1      0.2     108.3       2.3   2144057172  872444176

# iostat | grep hdisk1
hdisk1           0.1       6.3       0.5   63345700  112431123

#iostat|grep "hdisk1"|sed -e"s/[ ]*/ /g"
 h d i s k 1 0 . 1 6 . 3 0 . 5 6 3 3 4 5 8 8 0 1 1 2 4 3 2 3 5 4

sed debería buscar y reemplazar (s) múltiples espacios (/ [] * /) con un solo espacio (/ /) para todo el grupo (/ g) ... pero no solo está haciendo eso ... está espaciando cada carácter.

¿Qué estoy haciendo mal? Sé que tiene que ser algo simple ... AIX 5300-06

editar: Tengo otra computadora que tiene más de 10 discos duros. Estoy usando esto como parámetro para otro programa con fines de monitoreo.

El problema con el que me encontré fue que "awk '{print $ 5}' no funcionó porque estoy usando $ 1, etc. en la etapa secundaria y di errores con el comando Imprimir. Estaba buscando una versión grep / sed / cut Lo que parece funcionar es:

iostat | grep "hdisk1 " | sed -e's/  */ /g' | cut -d" " -f 5

Los [] s fueron "0 o más" cuando pensé que significaban "solo uno". Quitar los soportes lo hizo funcionar. Tres respuestas muy buenas realmente hacen que sea difícil elegir la "respuesta".

WernerCD
fuente

Respuestas:

52

El uso de grepes redundante, sedpuede hacer lo mismo. El problema está en el uso de *esa coincidencia también en 0 espacios, debe usar \+en su lugar:

iostat | sed -n '/hdisk1/s/ \+/ /gp'

Si sedno es compatible con \+metachar, entonces

iostat | sed -n '/hdisk1/s/  */ /gp'
enzotib
fuente
AIX no parece admitir +, pero la eliminación de [] parece haber hecho el truco.
WernerCD
Intenté usar la versión sed -n ... lo que sucede es que tengo otra computadora que tiene más de 10 unidades, así que comienza a hacer 1, 10, 11, etc. Intenté agregar un espacio / hdisk1 / y me dio una "función no reconocida". lo que parece funcionar es >> iostat | grep "hdisk1" | sed -e's / * / / g '
WernerCD
67

/[ ]*/coincide con cero o más espacios, por lo que la cadena vacía entre caracteres coincide.

Si está intentando hacer coincidir "uno o más espacios", use uno de estos:

... | sed 's/  */ /g'
... | sed 's/ \{1,\}/ /g'
... | tr -s ' '
Glenn Jackman
fuente
Ahh ... [] lo hace "opcional". Eso lo explica.
WernerCD
55
@WernerCD, no lo *hace "opcional". [ ]solo hace una lista de caracteres con un solo personaje (un espacio). Es el cuantificador *que significa "cero o más de lo anterior"
Glenn Jackman
Ahh ... así que para ser más precisos, cambiarlo de un solo espacio / * / a un doble espacio es lo que hizo entonces. Me tengo
WernerCD
Estaba tratando de buscar un patrón que buscara solo espacios dobles y funcionó bien
minhas23
66
+1 para la tr -s ' 'solución más simple
Andrejs
12

Cambie su *operador a a +. Estás haciendo coincidir cero o más del carácter anterior, que coincide con cada carácter porque todo lo que no es un espacio es ... um ... cero instancias de espacio. Necesitas hacer coincidir UNO o más. En realidad, sería mejor combinar dos o más

La clase de caracteres entre corchetes tampoco es necesaria para hacer coincidir un carácter. Solo puedes usar:

s/  \+/ /g

... a menos que quiera hacer coincidir las pestañas u otros tipos de espacios también, entonces la clase de caracteres es una buena idea.

Caleb
fuente
AIX no parece soportar +.
WernerCD
1
@WernerCD: Luego inténtalo s/ */ /g(con tres espacios, el formato del comentario los está colapsando). El operador de estrella hará que el carácter anterior sea opcional, por lo que si desea hacer coincidir dos o más con él, debe unir los dos primeros usted mismo (dos espacios) y luego agregar un tercer espacio y una estrella para hacer que el tercero y los siguientes espacios sean opcionales.
Caleb
3
@userunknown: en realidad no estoy mezclando dos cosas, todos los demás lo están :) Reemplazar un solo espacio con un solo espacio no tiene sentido, solo necesitas hacer esta acción en los partidos que tienen al menos dos espacios secuenciales. Dos espacios en blanco y un más o tres espacios en blanco y una estrella son exactamente lo que se necesita.
Caleb
@userunknown: No es tan importante, es solo una pérdida de un poco de tiempo de procesamiento y arroja cosas como contadores de partidos.
Caleb
8

Siempre puede coincidir con la última aparición en una secuencia de algo como:

s/\(sequence\)*/\1/

Y entonces estás en el camino correcto, pero en lugar de reemplazar la secuencia con un espacio, reemplázalo con su última aparición, un solo espacio. De esta forma si una secuencia de espacios se igualó a continuación, la secuencia se reduce a un solo espacio, pero si la cadena nula se compara entonces la cadena nula se reemplaza por sí mismo - y no hay daño, no hay falta. Así por ejemplo:

sed 's/\( \)*/\1/g' <<\IN                                    
# iostat
System configuration: lcpu=4 drives=8 paths=2 vdisks=0

tty:      tin         tout    avg-cpu: % user % sys % idle % iowait
          0.2         31.8                9.7   4.9   82.9      2.5

Disks:        % tm_act     Kbps      tps    Kb_read   Kb_wrtn
hdisk9           0.2      54.2       1.1   1073456960  436765896
hdisk7           0.2      54.1       1.1   1070600212  435678280
hdisk8           0.0       0.0       0.0          0         0
hdisk6           0.0       0.0       0.0          0         0
hdisk1           0.1       6.3       0.5   63344916  112429672
hdisk0           0.1       5.0       0.2   40967838  98574444
cd0              0.0       0.0       0.0          0         0
hdiskpower1      0.2     108.3       2.3   2144057172  872444176

# iostat | grep hdisk1
hdisk1           0.1       6.3       0.5   63345700  112431123

IN

SALIDA

# iostat
System configuration: lcpu=4 drives=8 paths=2 vdisks=0

tty: tin tout avg-cpu: % user % sys % idle % iowait
 0.2 31.8 9.7 4.9 82.9 2.5

Disks: % tm_act Kbps tps Kb_read Kb_wrtn
hdisk9 0.2 54.2 1.1 1073456960 436765896
hdisk7 0.2 54.1 1.1 1070600212 435678280
hdisk8 0.0 0.0 0.0 0 0
hdisk6 0.0 0.0 0.0 0 0
hdisk1 0.1 6.3 0.5 63344916 112429672
hdisk0 0.1 5.0 0.2 40967838 98574444
cd0 0.0 0.0 0.0 0 0
hdiskpower1 0.2 108.3 2.3 2144057172 872444176

# iostat | grep hdisk1
hdisk1 0.1 6.3 0.5 63345700 112431123

Dicho todo esto, probablemente sea mucho mejor evitar las expresiones regulares por completo en esta situación y hacer lo siguiente:

tr -s \  <infile
mikeserv
fuente
44
+1 por la simplicidad de la respuesta real,iostat | tr -s \
Comodín
'tr -s \' es lo mismo que 'tr -s ""'. Me hizo darme cuenta de que el espacio se puede pasar como argumento en la cadena escapando con "\". Veo que también se puede usar en scripts de shell. Aplicación genial
randominstanceOfLivingThing
5

Tenga en cuenta que también puede hacer lo que intenta, es decir

iostat | grep "hdisk1 " | sed -e's/  */ /g' | cut -d" " -f 5

por

iostat | while read disk tma kbps tps re wr; do [ "$disk" = "hdisk1" ] && echo "$re"; done

lo cual podría ser especialmente útil si luego intentas acceder a otros campos y / o calcular algo, como este:

iostat | while read disk tma kbps tps re wr; do [ "$disk" = "hdisk1" ] && echo "$(( re/1024 )) Mb"; done
rozcietrzewiacz
fuente
Muy agradable. La primera versión funciona. A mis cajas AIX no parece gustarles la segunda. Salida de las tres cajas: "$ [re / 1024] Mb". La herramienta de monitoreo que estoy usando tiene conversiones para informes, por lo que no es una cosa "necesaria" para mí, pero me gusta.
WernerCD
@enzotib Gracias por corregir el while.
rozcietrzewiacz
@WernerCD Ah, esto $[ .. ]probablemente esté disponible en versiones recientes de bash (quizás también zsh). Actualicé la respuesta a una más portátil en su $(( .. ))lugar.
rozcietrzewiacz
Eso hizo el truco. Tendré que buscar eso. Elegante.
WernerCD
0

Puede usar el siguiente script para convertir múltiples espacios en un solo espacio, una TAB o cualquier otra cadena:

$ ls | compress_spaces.sh       # converts multiple spaces to one
$ ls | compress_spaces.sh TAB   # converts multiple spaces to a single tab character
$ ls | compress_spaces.sh TEST  # converts multiple spaces to the phrase TEST
$ compress_spaces.sh help       # show the help for this command

compress_spaces.sh

function show_help()
{
  IT=$(CAT <<EOF

  usage: {REPLACE_WITH}

  NOTE: If you pass in TAB, then multiple spaces are replaced with a TAB character

  no args -> multiple spaces replaced with a single space
  TAB     -> multiple spaces replaced with a single tab character
  TEST    -> multiple spaces replaced with the phrase "TEST"

  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

# Show help if we're not getting data from stdin
if [ -t 0 ]; then
  show_help
fi

REPLACE_WITH=${1:-' '}

if [ "$REPLACE_WITH" == "tab" ]
then
  REPLACE_WITH=$'\t'
fi
if [ "$REPLACE_WITH" == "TAB" ]
then
  REPLACE_WITH=$'\t'
fi

sed "s/ \{1,\}/$REPLACE_WITH/gp"
Brad Parks
fuente