Wie entferne ich mit sed mehrere Leerzeichen zu einem?

69

sedunter AIX tut nicht was ich denke es sollte. Ich versuche, mehrere Leerzeichen durch ein einzelnes Leerzeichen in der Ausgabe von IOSTAT zu ersetzen:

# 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 sollte nach mehreren Leerzeichen (/ [] * /) suchen und diese durch ein einzelnes Leerzeichen (/ /) für die gesamte Gruppe (/ g) ersetzen.

Was mache ich falsch? Ich weiß, dass es etwas Einfaches sein muss ... AIX 5300-06

Bearbeiten: Ich habe einen anderen Computer mit mehr als 10 Festplatten. Ich verwende dies als Parameter für ein anderes Programm zu Überwachungszwecken.

Das Problem, auf das ich gestoßen bin, war, dass "awk '{print $ 5}' nicht funktioniert hat, weil ich $ 1 usw. in der Sekundärstufe verwende und Fehler mit dem Befehl Drucken ausgegeben habe. Ich habe nach einer grep / sed / cut-Version gesucht Was zu funktionieren scheint, ist:

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

Die [] s waren "0 oder mehr", als ich dachte, dass sie "nur eine" bedeuten. Das Entfernen der Klammern hat funktioniert. Drei sehr gute Antworten machen es sehr schnell schwer, die "Antwort" zu wählen.

WernerCD
quelle

Antworten:

52

Die Verwendung von grepist redundant, sedkann das gleiche tun. Das Problem ist, dass bei der Verwendung *dieser Übereinstimmung auch 0 Leerzeichen verwendet \+werden müssen:

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

Wenn Ihr sedMetacar nicht unterstützt \+, tun Sie dies

iostat | sed -n '/hdisk1/s/  */ /gp'
Enzotib
quelle
AIX scheint + nicht zu unterstützen, aber das Entfernen der [] scheint den Trick getan zu haben.
WernerCD
Ich habe versucht, die sed -n-Version zu verwenden ... was passiert, ist, dass ich einen anderen Computer habe, der 10+ Laufwerke hat, so dass er 1, 10, 11 usw. ausführt. Ich habe versucht, ein Leerzeichen / hdisk1 / hinzuzufügen, und es hat mir gegeben eine "nicht erkannte Funktion". was zu funktionieren scheint, ist >> iostat | grep "hdisk1" | sed -e's / * / / g '
WernerCD
67

/[ ]*/Entspricht keinem oder mehreren Leerzeichen, sodass die leere Zeichenfolge zwischen den Zeichen übereinstimmt.

Wenn Sie versuchen, "ein oder mehrere Leerzeichen" zuzuordnen, verwenden Sie eines der folgenden Elemente:

... | sed 's/  */ /g'
... | sed 's/ \{1,\}/ /g'
... | tr -s ' '
Glenn Jackman
quelle
Ahh ... [] macht es "optional". Das erklärt es.
WernerCD
5
@WernerCD, nein *macht es "optional". [ ]Erstellt einfach eine Liste von Zeichen mit nur einem Zeichen (einem Leerzeichen). Es ist der Quantor *, der "null oder mehr von dem vorherigen Ding" bedeutet
Glenn Jackman
Ahh ... genauer gesagt, es wurde dann von einem einzelnen Leerzeichen (*) in ein doppeltes Leerzeichen geändert. Ich muss.
WernerCD
Ich habe versucht, nach einem Muster zu suchen, das nur doppelte Leerzeichen enthält, und es hat gut funktioniert
minhas23
6
+1 für die einfachste tr -s ' 'Lösung
Andrejs
12

Ändern Sie Ihren *Operator in a +. Sie stimmen mit mindestens null Zeichen des vorherigen Zeichens überein, das mit jedem Zeichen übereinstimmt, da alles, was kein Leerzeichen ist, ... ähm ... keine Leerzeichen enthält. Sie müssen mit EINEM oder mehreren übereinstimmen. Eigentlich wäre es besser, zwei oder mehr zusammenzubringen

Die in eckige Klammern gesetzte Zeichenklasse ist auch nicht erforderlich, um ein Zeichen abzugleichen. Sie können einfach verwenden:

s/  \+/ /g

... wenn Sie nicht auch Tabulatoren oder andere Arten von Leerzeichen zuordnen möchten, ist die Zeichenklasse eine gute Idee.

Caleb
quelle
AIX scheint + nicht zu unterstützen.
WernerCD
1
@WernerCD: Dann versuche es s/ */ /g(das ist mit drei Leerzeichen, die Kommentarformatierung reduziert sie). Der Sternoperator macht das vorherige Zeichen optional. Wenn Sie also zwei oder mehr Zeichen damit verbinden möchten, müssen Sie die ersten beiden selbst (zwei Leerzeichen) verbinden. Fügen Sie dann ein drittes Leerzeichen und einen Stern hinzu, um das dritte und die folgenden Leerzeichen optional zu machen.
Caleb
3
@userunknown: Eigentlich mische ich gar nicht zwei Dinge, alle anderen sind es :) Das Ersetzen eines einzelnen Feldes durch ein einzelnes Feld ist sinnlos. Sie müssen diese Aktion nur für Matches ausführen, die mindestens zwei aufeinanderfolgende Felder haben. Zwei Leerzeichen und ein Pluszeichen oder drei Leerzeichen und ein Stern sind genau das, was benötigt wird.
Caleb
@userunknown: Es ist keine so große Sache, es ist nur eine Verschwendung von ein bisschen Verarbeitungszeit und es wirft Dinge wie Spielzähler ab.
Caleb
8

Sie können immer das letzte Vorkommen in einer Sequenz wie der folgenden zuordnen:

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

Und so sind Sie auf dem richtigen Weg, aber anstatt die Sequenz durch ein Leerzeichen zu ersetzen, ersetzen Sie sie durch das letzte Vorkommen - ein einzelnes Leerzeichen. Auf diese Weise , wenn eine Folge von Räumen wird dann abgestimmt die Sequenz zu einem einzigen Raum reduziert wird, aber wenn der Null - String abgestimmt wird dann der Null - String mit mir selbst ersetzt - und kein Schaden, kein Foul. Also zum Beispiel:

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

AUSGABE

# 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

Trotzdem ist es wahrscheinlich weitaus besser, Regexps in dieser Situation vollständig zu vermeiden und stattdessen Folgendes zu tun:

tr -s \  <infile
mikeserv
quelle
4
+1 für die Einfachheit der realen Antwort,iostat | tr -s \
Wildcard
'tr -s \' ist dasselbe wie 'tr -s ""'. Mir wurde klar, dass Leerzeichen als Argument in der Zeichenfolge übergeben werden können, indem Sie mit "\" maskieren. Ich sehe, dass es auch in Shell-Skripten verwendet werden kann. Coole Anwendung.
randominstanceOfLivingThing
5

Beachten Sie, dass Sie auch das tun können, was Sie versuchen

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

durch

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

Dies kann besonders nützlich sein, wenn Sie später versuchen, auch auf andere Felder zuzugreifen und / oder etwas zu berechnen.

iostat | while read disk tma kbps tps re wr; do [ "$disk" = "hdisk1" ] && echo "$(( re/1024 )) Mb"; done
rozcietrzewiacz
quelle
Sehr schön. Erste Version funktioniert. Meine AIX-Boxen scheinen die zweite nicht zu mögen. Alle drei Boxen geben aus: "$ [re / 1024] Mb". Das Überwachungstool, das ich verwende, enthält Konvertierungen für Berichte, sodass es für mich nicht "erforderlich" ist, aber ich mag es.
WernerCD
@enzotib Danke für die Korrektur des while.
Rozcietrzewiacz
@WernerCD Ah, das $[ .. ]ist wahrscheinlich in neueren Versionen von bash verfügbar (vielleicht auch zsh). Ich habe die Antwort $(( .. ))stattdessen auf eine tragbarere Version aktualisiert .
Rozcietrzewiacz
Das hat den Trick gemacht. Ich muss das nachschlagen. Snazzy.
WernerCD
0

Mit dem folgenden Skript können Sie mehrere Leerzeichen in ein einzelnes Leerzeichen, ein TAB oder eine andere Zeichenfolge konvertieren:

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