Como retirar vários espaços para um usando sed?

69

sedno AIX não está fazendo o que eu acho que deveria. Estou tentando substituir vários espaços por um único espaço na saída do 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

O sed deve procurar e substituir vários espaços (/ [] * /) por um único espaço (/ /) para todo o grupo (/ g) ... mas não é apenas isso: espaçar cada caractere.

O que estou fazendo errado? Eu sei que tem que ser algo simples ... AIX 5300-06

editar: eu tenho outro computador com mais de 10 discos rígidos. Estou usando isso como um parâmetro para outro programa para fins de monitoramento.

O problema que encontrei foi que "awk '{print $ 5}' não funcionou porque estou usando $ 1 etc. no estágio secundário e deu erros com o comando Print. Estava procurando uma versão grep / sed / cut O que parece funcionar é:

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

Os [] eram "0 ou mais" quando pensei que eles queriam dizer "apenas um". A remoção dos suportes deu certo. Três respostas muito boas tornam muito difícil escolher a "resposta".

WernerCD
fonte

Respostas:

52

O uso de grepé redundante, sedpode fazer o mesmo. O problema está no uso *dessa correspondência também em 0 espaços, você deve usar \+:

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

Se o seu sednão suporta \+metacar, então faça

iostat | sed -n '/hdisk1/s/  */ /gp'
enzotib
fonte
O AIX não parece suportar o +, mas a remoção dos [] parece ter feito o truque.
precisa saber é o seguinte
Eu tentei usar a versão sed -n ... o que acontece é que eu tenho outro computador com mais de 10 unidades, então ele começa a executar 1, 10, 11, etc ... Eu tentei adicionar um espaço / hdisk1 / e isso me deu uma "função não reconhecida". o que parece funcionar é >> iostat | grep "hdisk1" | / / / g 'de sed -e'
WernerCD
67

/[ ]*/corresponde a zero ou mais espaços; portanto, a string vazia entre os caracteres corresponde.

Se você estiver tentando combinar "um ou mais espaços", use um destes:

... | sed 's/  */ /g'
... | sed 's/ \{1,\}/ /g'
... | tr -s ' '
Glenn Jackman
fonte
Ahh ... [] torna "opcional". Isso explica tudo.
WernerCD
5
@WernerCD, no *torna "opcional". [ ]apenas faz uma lista de caracteres com apenas um caractere (um espaço). É o quantificador *que significa "zero ou mais da expressão anterior"
Glenn Jackman
Ahh ... para ser mais preciso, alterá-lo de um espaço único / * / para um espaço duplo é o que ele fez então. Eu entendi.
WernerCD
Eu estava tentando procurar um padrão que pesquisasse apenas espaços duplos e funcionasse bem
minhas23
6
+1 para a mais simples tr -s ' 'solução
Andrejs
12

Mude o seu *operador para a +. Você está correspondendo a zero ou mais caracteres anteriores, o que corresponde a todos os caracteres, porque tudo o que não é um espaço é ... um ... zero instâncias de espaço. Você precisa corresponder a UM ou mais. Na verdade, seria melhor combinar dois ou mais

A classe de caracteres entre colchetes também não é necessária para combinar um caractere. Você pode apenas usar:

s/  \+/ /g

... a menos que você queira combinar tabulações ou outros tipos de espaços também, a classe de caracteres é uma boa ideia.

Caleb
fonte
O AIX parece não suportar o +.
precisa saber é o seguinte
11
@ WernerCD: Em seguida, tente s/ */ /g(ou seja, com três espaços, a formatação do comentário os está recolhendo). O operador estrela fará com que o caractere anterior seja opcional; portanto, se você combinar dois ou mais com ele, precisará corresponder os dois primeiros (dois espaços) e adicione um terceiro espaço e uma estrela para tornar opcionais os terceiros e seguintes espaços.
Caleb
3
@userunknown: Na verdade, eu não estou misturando duas coisas, todo mundo está :) Substituir um único espaço por um único espaço é inútil, você só precisa fazer essa ação em correspondências que tenham pelo menos dois espaços sequenciais. Dois espaços em branco e um mais ou três espaços em branco e uma estrela são exatamente o que é necessário.
Caleb
@userunknown: Não é tão grande coisa, é apenas uma perda de tempo de processamento e gera coisas como contadores de partidas.
Caleb
8

Você sempre pode corresponder à última ocorrência em uma sequência de algo como:

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

E assim você está no caminho certo, mas em vez de substituir a sequência por um espaço - substitua-a pela sua última ocorrência - um único espaço. Dessa forma, se uma sequência de espaços for correspondida, a sequência será reduzida para um único espaço, mas se a sequência nula for correspondida, a sequência nula será substituída por ela mesma - e sem dano, sem falta. Então, por exemplo:

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

RESULTADO

# 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

Tudo isso dito, provavelmente é muito melhor evitar os regexps completamente nessa situação e, em vez disso:

tr -s \  <infile
mikeserv
fonte
4
+1 pela simplicidade da resposta real, #iostat | tr -s \
Wildcard
'tr -s \' é o mesmo que 'tr -s ""'. Me fez perceber que o espaço pode ser passado como argumento na string escapando com "\". Vejo que ele também pode ser usado em scripts de shell. Aplicação legal.
randominstanceOfLivingThing
5

Observe que você também pode fazer o que tenta, ou seja

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

o que pode ser especialmente útil se você tentar acessar posteriormente outros campos também e / ou calcular algo como:

iostat | while read disk tma kbps tps re wr; do [ "$disk" = "hdisk1" ] && echo "$(( re/1024 )) Mb"; done
rozcietrzewiacz
fonte
Muito agradável. Primeira versão funciona. Minhas caixas do AIX parecem não gostar da segunda. Todas as três caixas produzem: "$ [re / 1024] Mb". A ferramenta de monitoramento que estou usando possui conversões para relatórios, portanto não é uma coisa "necessária" para mim, mas eu gosto.
WernerCD
@enzotib Obrigado por corrigir o while.
rozcietrzewiacz
@ WernerCD Ah, isso $[ .. ]provavelmente está disponível nas versões recentes do bash (talvez zsh também). Atualizei a resposta para uma mais portátil $(( .. )).
rozcietrzewiacz
Isso fez o truque. Vou ter que procurar isso. Snazzy.
precisa saber é o seguinte
0

Você pode usar o script a seguir para converter vários espaços em um único espaço, uma TAB ou qualquer outra cadeia de caracteres:

$ 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
fonte