Jak rozebrać wiele spacji do jednego za pomocą sed?


69

sedw systemie AIX nie robi tego, co moim zdaniem powinno. Próbuję zastąpić wiele spacji jedną spacją w danych wyjściowych 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 powinien przeszukiwać i zamieniać wiele spacji (/ [] * /) pojedynczym spacją (/ /) dla całej grupy (/ g) ... ale nie tylko robi to ... odstępy między znakami.

Co ja robię źle? Wiem, że to musi być coś prostego ... AIX 5300-06

edycja: Mam inny komputer z ponad 10 dyskami twardymi. Używam tego jako parametru innego programu do celów monitorowania.

Problem, na który natrafiłem, polegał na tym, że „awk” {print 5 $} nie działał, ponieważ używam 1 $ itp. Na drugim etapie i dawałem błędy w poleceniu Drukuj. Szukałem wersji grep / sed / cut Wydaje się, że działa:

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

[] Były „0 lub więcej”, kiedy myślałem, że oznaczają „tylko jeden”. Usunięcie wsporników działało. Trzy bardzo dobre odpowiedzi bardzo szybko utrudniają wybór „odpowiedzi”.

Odpowiedzi:


52

Zastosowanie grepjest zbędne, sedmoże zrobić to samo. Problem polega na użyciu *tego dopasowania również 0 spacji, musisz \+zamiast tego użyć :

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

Jeśli sednie obsługuje \+metazar, to zrób

iostat | sed -n '/hdisk1/s/  */ /gp'

Wydaje się, że AIX nie obsługuje +, ale wydaje się, że usunięcie [] spowodowało załatwienie sprawy.
WernerCD,

Próbowałem użyć wersji sed -n ... co się dzieje, mam inny komputer, który ma ponad 10 dysków, więc zaczyna robić 1, 10, 11 itd. ... Próbowałem dodać spację / hdisk1 / i dało mi to „nierozpoznana funkcja”. wydaje się, że działa >> iostat | grep "hdisk1" | sed -e's / * / / g '
WernerCD

67

/[ ]*/dopasowuje zero lub więcej spacji, więc pusty ciąg między znakami pasuje.

Jeśli próbujesz dopasować „jedno lub więcej spacji”, użyj jednego z tych:

... | sed 's/  */ /g'
... | sed 's/ \{1,\}/ /g'
... | tr -s ' '

Ahh ... [] czyni to „opcjonalnym”. To wyjaśnia to.
WernerCD

5
@WernerCD, no nie *czyni go „opcjonalnym”. [ ]tworzy po prostu listę znaków z tylko jednym znakiem (spacją). Jest to kwantyfikator, *który oznacza „zero lub więcej z poprzedniej rzeczy”
glenn jackman

Achh ... żeby być bardziej precyzyjnym, zmieniając go z pojedynczej spacji / * / na podwójną spację, to wtedy to zrobiło. Gottcha.
WernerCD

Próbowałem znaleźć wzór, który wyszukuje tylko podwójne spacje i zadziałało
minhas23

6
+1 za najprostsze tr -s ' 'rozwiązanie
Andrejs

12

Zmień *operatora na +. Dopasowujesz zero lub więcej poprzedniej postaci, która pasuje do każdej postaci, ponieważ wszystko, co nie jest spacją, jest ... hm ... zerowym wystąpieniem spacji. Musisz dopasować JEDEN lub więcej. W rzeczywistości lepiej byłoby dopasować dwa lub więcej

Klasa znaków w nawiasach kwadratowych nie jest również konieczna do dopasowania jednego znaku. Możesz po prostu użyć:

s/  \+/ /g

... chyba że chcesz dopasować tabulatory lub inne rodzaje spacji, klasa postaci jest dobrym pomysłem.


Wydaje się, że AIX nie obsługuje +.
WernerCD,

1
@WernerCD: Następnie spróbuj s/ */ /g(to z trzema spacjami, formatowanie komentarzy je zwija ). Operator gwiazdy sprawi, że poprzedni znak będzie opcjonalny, więc jeśli dopasujesz dwa lub więcej, musisz dopasować pierwsze dwa samodzielnie (dwa pola), a następnie dodaj trzecie pole i gwiazdkę, aby trzecie i następne pola były opcjonalne.
Caleb

3
@ userunknown: Właściwie nie mieszam dwóch rzeczy, wszyscy są inni :) Zastąpienie pojedynczej spacji pojedynczą spacją jest bezcelowe, wystarczy wykonać tę akcję na dopasowaniach, które mają co najmniej dwie spacje sekwencyjne. Dwie puste i plus lub trzy puste i gwiazda są dokładnie tym, czego potrzeba.
Caleb

@userunknown: To nie jest wielka sprawa, to tylko strata trochę czasu przetwarzania i wyrzuca takie rzeczy, jak liczniki zapałek.
Caleb,

8

Zawsze możesz dopasować ostatnie wystąpienie w sekwencji czegoś takiego jak:

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

I tak jesteś na dobrej drodze, ale zamiast zastępować sekwencję spacją - zastąp ją ostatnim wystąpieniem - pojedynczą spacją. W ten sposób, jeśli sekwencja spacji zostanie dopasowana, sekwencja zostanie zredukowana do pojedynczej spacji, ale jeśli dopasowany zostanie łańcuch zerowy, wówczas łańcuch zerowy zostanie zastąpiony samym sobą - bez szkody, bez faulu. Na przykład:

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

WYNIK

# 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

To powiedziawszy, prawdopodobnie lepiej jest całkowicie uniknąć wyrażeń regularnych w tej sytuacji i zamiast tego:

tr -s \  <infile

4
+1 za prostotę prawdziwej odpowiedzi,iostat | tr -s \
Wildcard

„tr -s \” jest taki sam jak „tr -s” ”. Uświadomiłem sobie, że spacja może zostać przekazana jako argument w ciągu znaków poprzez ucieczkę z „\”. Widzę, że można go również używać w skryptach powłoki. Fajna aplikacja.
randominstanceOfLivingThing

5

Zauważ, że możesz także robić to, co próbujesz

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

przez

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

co może być szczególnie przydatne, jeśli później spróbujesz uzyskać dostęp do innych pól i / lub coś obliczyć - na przykład:

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

Bardzo dobrze. Pierwsza wersja działa. Moje urządzenia AIX nie wydają się lubić drugiego. Wszystkie trzy pola wyjściowe: „$ [re / 1024] Mb”. Narzędzie do monitorowania, którego używam, ma konwersje do raportów, więc nie jest to dla mnie „potrzebna” rzecz, ale mi się podoba.
WernerCD

@enzotib Dziękujemy za poprawienie while.
rozcietrzewiacz

@WernerCD Ah, to $[ .. ]prawdopodobnie jest dostępne w najnowszych wersjach bash (być może także zsh). $(( .. ))Zamiast tego zaktualizowałem odpowiedź na bardziej przenośną .
rozcietrzewiacz

To załatwiło sprawę. Będę musiał to sprawdzić. Odlotowy.
WernerCD

0

Możesz użyć następującego skryptu, aby przekonwertować wiele spacji na pojedynczą spację, TAB lub dowolny inny ciąg:

$ 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"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.