Comment effacer plusieurs espaces en utilisant sed?

69

sedsur AIX ne fait pas ce que je pense qu'il devrait. J'essaie de remplacer plusieurs espaces par un seul espace dans la sortie d'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 devrait chercher et remplacer plusieurs espaces (/ [] * /) par un seul espace (/ /) pour tout le groupe (/ g) ... mais ce n'est pas seulement le cas ... espacer chaque caractère.

Qu'est-ce que je fais mal? Je sais que c'est quelque chose de simple ... AIX 5300-06

edit: J'ai un autre ordinateur qui a plus de 10 disques durs. J'utilise cela comme paramètre pour un autre programme à des fins de surveillance.

Le problème que j'ai rencontré était que "awk '{print $ 5}" ne fonctionnait pas parce que j'utilisais $ 1, etc. à l'étape secondaire et que je commettais des erreurs avec la commande Print. Je cherchais une version de grep / sed / cut Ce qui semble fonctionner est:

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

Les [] étaient "0 ou plus" quand je pensais qu'ils signifiaient "un seul". En enlevant les supports, ça a fonctionné. Trois très bonnes réponses rendent très difficile le choix de la "réponse".

WernerCD
la source

Réponses:

52

L'utilisation de grepest redondant, sedpeut faire la même chose. Le problème est dans l'utilisation de *cette correspondance également 0 espaces, vous devez utiliser à la \+place:

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

Si votre sedne supporte pas \+metachar, alors faites

iostat | sed -n '/hdisk1/s/  */ /gp'
enzotib
la source
AIX ne semble pas supporter +, mais la suppression des [] semble avoir fait l'affaire.
WernerCD
J'ai essayé d'utiliser la version sed -n ... ce qui se passe, c'est que j'ai un autre ordinateur qui a plus de 10 lecteurs, alors il commence à faire 1, 10, 11, etc. une "fonction non reconnue". ce qui semble fonctionner est >> iostat | grep "hdisk1" | sed -e's / * / / g '
WernerCD
67

/[ ]*/correspond à zéro ou plusieurs espaces, de sorte que la chaîne vide entre les caractères correspond.

Si vous essayez de faire correspondre "un ou plusieurs espaces", utilisez l'une de ces méthodes:

... | sed 's/  */ /g'
... | sed 's/ \{1,\}/ /g'
... | tr -s ' '
Glenn Jackman
la source
Ahh ... [] le rend "optionnel". Ceci explique cela.
WernerCD
5
@WernerCD, no le *rend "optionnel". [ ]fait juste une liste de caractères avec un seul caractère dedans (un espace). C'est le quantificateur *qui signifie "zéro ou plus de la chose précédente"
glenn jackman
Ahh ... donc pour être plus précis, le changer d'un espace simple / * /, à un double espace est ce qui l'a fait alors. Je Gottcha.
WernerCD
J'essayais de chercher un motif qui ne cherchait que les doubles espaces et qui fonctionnait bien
minhas23
6
+1 pour la tr -s ' 'solution la plus simple
Andrejs
12

Changez votre *opérateur en a +. Vous correspondez à zéro ou plus du caractère précédent, ce qui correspond à chaque caractère car tout ce qui n'est pas un espace est ... euh ... zéro instance d'espace. Vous devez faire correspondre un ou plusieurs. En fait, il vaudrait mieux faire correspondre deux ou plus

La classe de caractères entre crochets est également inutile pour faire correspondre un caractère. Vous pouvez simplement utiliser:

s/  \+/ /g

... sauf si vous souhaitez également faire correspondre des tabulations ou d'autres types d'espaces, la classe de caractères est une bonne idée.

Caleb
la source
AIX ne semble pas supporter +.
WernerCD
1
@WernerCD: Ensuite, essayez s/ */ /g(avec trois espaces, la mise en forme du commentaire les réduit ). L'opérateur en étoile rendra le caractère précédent facultatif. Par conséquent, si vous voulez en faire correspondre deux ou plus, vous devez faire correspondre les deux premiers (deux espaces), puis ajoutez un troisième espace et une étoile pour que les troisième et suivants soient facultatifs.
Caleb
3
@userunknown: En fait, je ne mélange pas deux choses, mais tout le monde :) Deux blancs et un plus ou trois blancs et une étoile sont exactement ce dont vous avez besoin.
Caleb
@userunknown: Ce n'est pas une grosse affaire, c'est juste une perte de temps de traitement et cela jette des choses comme des marqueurs de correspondance.
Caleb
8

Vous pouvez toujours faire correspondre la dernière occurrence dans une séquence de quelque chose comme:

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

Et vous êtes donc sur la bonne voie, mais au lieu de remplacer la séquence par un espace, remplacez-la par sa dernière occurrence, un espace unique. De cette façon , si une suite d'espaces est adaptée, la séquence est réduite à un seul espace, mais si la chaîne vide correspond alors à la chaîne vide est remplacée par elle - même - et aucun mal, aucune faute. Donc, par exemple:

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

SORTIE

# 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

Cela dit, il vaut probablement mieux éviter les expressions rationnelles dans cette situation et faire plutôt:

tr -s \  <infile
Mikeserv
la source
4
+1 pour la simplicité de la vraie réponse,iostat | tr -s \
Wildcard
'tr -s \' est identique à 'tr -s ""'. M'a fait comprendre que l'espace peut être passé comme argument dans la chaîne en s'échappant avec "\". Je vois qu'il peut également être utilisé dans des scripts shell. Application cool.
randominstanceOfLivingThing
5

Notez que vous pouvez aussi faire ce que vous essayez, c’est-à-dire

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

par

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

ce qui peut être particulièrement utile si vous tentez ultérieurement d'accéder à d'autres champs et / ou de calculer quelque chose - comme ceci:

iostat | while read disk tma kbps tps re wr; do [ "$disk" = "hdisk1" ] && echo "$(( re/1024 )) Mb"; done
rozcietrzewiacz
la source
Très agréable. La première version fonctionne. Mes boîtes AIX ne semblent pas aimer la seconde. Les trois boîtes produisent: "$ [re / 1024] Mo". L'outil de surveillance que j'utilise comporte des conversions pour les rapports. Ce n'est donc pas une chose "nécessaire" pour moi, mais j'aime bien.
WernerCD
@enzotib Merci d'avoir corrigé le while.
rozcietrzewiacz
@WernerCD Ah, $[ .. ]c'est probablement disponible dans les versions récentes de bash (peut-être aussi zsh). J'ai mis à jour la réponse à un plus portable à la $(( .. ))place.
rozcietrzewiacz
Cela a fait le tour. Je vais devoir regarder ça. Snazzy.
WernerCD
0

Vous pouvez utiliser le script suivant pour convertir plusieurs espaces en un seul espace, une tabulation ou toute autre chaîne:

$ 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
la source