Как разделить несколько пробелов на один с помощью sed?

69

sedна AIX не делает то, что я думаю, что должно. Я пытаюсь заменить несколько пробелов одним пробелом в выводе 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 должен искать и заменять (-ять) несколько пробелов (/ [] * /) одним пробелом (/ /) для всей группы (/ g) ... но он не только делает это ... он разделяет каждый символ.

Что я делаю неправильно? Я знаю, это должно быть что-то простое ... AIX 5300-06

редактировать: у меня есть другой компьютер, который имеет более 10 жестких дисков. Я использую это как параметр для другой программы для целей мониторинга.

Проблема, с которой я столкнулся, заключалась в том, что «awk» {print $ 5} 'не работал, потому что я использую $ 1 и т. Д. На втором этапе и выдавал ошибки командой Print. Я искал версию grep / sed / cut Кажется, что работает:

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

[] Были "0 или больше", когда я думал, что они означают "только один". Сняв скобки, все заработало. Три очень хороших ответа очень быстро затрудняют выбор «ответа».

WernerCD
источник

Ответы:

52

Использование grepизбыточно, sedможно сделать то же самое. Проблема в том, *что в этом совпадении также 0 пробелов, \+вместо этого вы должны использовать :

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

Если ваш sedне поддерживает \+metachar, то сделайте

iostat | sed -n '/hdisk1/s/  */ /gp'
enzotib
источник
AIX, похоже, не поддерживает +, но удаление [], похоже, помогло.
WernerCD
Я попытался использовать версию sed -n ... что происходит, у меня есть другой компьютер с 10+ дисками, поэтому он начинает делать 1, 10, 11 и т. Д. ... Я попытался добавить пробел / hdisk1 /, и он дал мне «не распознанная функция». что похоже на работу это >> iostat | grep "hdisk1" | sed -e's / * / / g '
WernerCD
67

/[ ]*/соответствует нулю или более пробелов, поэтому пустая строка между символами совпадает.

Если вы пытаетесь сопоставить «один или несколько пробелов», используйте один из них:

... | sed 's/  */ /g'
... | sed 's/ \{1,\}/ /g'
... | tr -s ' '
Гленн Джекман
источник
Ааа ... [] делает это "необязательным". Что объясняет его.
WernerCD
5
@WernerCD, no *делает его «необязательным». [ ]просто составляет список символов только с одним символом (пробел). Это квантификатор, *который означает «ноль или более от предыдущей вещи»
Гленн Джекман
Ааа ... так что если быть более точным, то изменив это с одного пробела / * / на двойной пробел, это то, что он сделал тогда Я понял.
WernerCD
Я пытался найти шаблон, который ищет только двойные пробелы, и он работал круто
minhas23
6
+1 за самое простое tr -s ' 'решение
Андрейс
12

Измените вашего *оператора на +. Вы соответствуете нулю или нескольким предыдущим символам, что соответствует каждому символу, потому что все, что не является пробелом, это ... ну ... нулевые экземпляры пробела. Вы должны соответствовать ОДИН или больше. На самом деле было бы лучше сопоставить два или более

Класс символов в квадратных скобках также не требуется для сопоставления одного символа. Вы можете просто использовать:

s/  \+/ /g

... если вы не хотите сопоставлять табуляцию или другие виды пробелов, то класс символов будет хорошей идеей.

Калеб
источник
AIX, похоже, не поддерживает +.
WernerCD
1
@WernerCD: Затем попробуйте s/ */ /g(это с тремя пробелами, форматирование комментариев сворачивает их). Оператор звездочки сделает предыдущий символ необязательным, поэтому, если вы хотите сопоставить его с двумя или более символами, вам нужно сопоставить первые два самостоятельно (два пробела), затем добавить третий пробел и звезду, чтобы сделать третий и последующие пробелы необязательными.
Калеб
3
@userunknown: На самом деле я вообще не смешиваю две вещи, все остальные :) Замена одного пробела одним пробелом не имеет смысла, вам нужно выполнять это действие только в тех матчах, которые имеют как минимум два последовательных пробела. Два пробела и плюс или три пробела и звезда - это именно то, что нужно.
Калеб
@userunknown: Это не так уж и важно, это просто пустая трата времени на обработку и сбрасывает такие вещи, как счетчики совпадений.
Калеб
8

Вы всегда можете сопоставить последнее вхождение в последовательности, например:

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

И поэтому вы на правильном пути, а не заменяете последовательность пробелом - замените его последним вхождением - одним пробелом. Таким образом , если последовательность пространств будет соответствовать , то последовательность не сводится к одному пространству, но если нулевая строка совпадает , то пустая строка заменяется сама по себе - и никакого вреда, нет фола. Так, например:

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

ВЫХОД

# 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

С учетом всего вышесказанного, вероятно, в этой ситуации гораздо лучше избегать регулярных выражений и делать вместо этого:

tr -s \  <infile
mikeserv
источник
4
+1 за простоту реального ответа,iostat | tr -s \
Wildcard
'tr -s \' совпадает с 'tr -s' ''. Заставил меня осознать, что пробел может быть передан в качестве аргумента в строке путем экранирования "\". Я вижу, что это может быть использовано и в сценариях оболочки. Классное приложение.
randominstanceOfLivingThing
5

Обратите внимание, что вы также можете делать то, что вы пытаетесь, то есть

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

по

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

что может быть особенно полезно, если вы позже попытаетесь получить доступ и к другим полям и / или рассчитать что-то вроде этого:

iostat | while read disk tma kbps tps re wr; do [ "$disk" = "hdisk1" ] && echo "$(( re/1024 )) Mb"; done
rozcietrzewiacz
источник
Очень хорошо. Первая версия работает. Мои AIX-боксы, похоже, не нравятся вторым. Все три поля выводят: «$ [re / 1024] Мб». У инструмента мониторинга, который я использую, есть конверсии для отчетов, поэтому он мне не нужен, но мне нравится.
WernerCD
@enzotib Спасибо за исправление while.
rozcietrzewiacz
@WernerCD А, это $[ .. ]возможно в последних версиях bash (возможно, и в zsh). Я обновил ответ на более портативный $(( .. ))вместо этого.
rozcietrzewiacz
Это добилось цели. Я должен это посмотреть. Snazzy.
WernerCD
0

Вы можете использовать следующий скрипт для преобразования нескольких пробелов в один пробел, TAB или любую другую строку:

$ 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"
Брэд Паркс
источник