sed Диапазон символов соответствия

Только для Bash:

# VERSION 1

while IFS=$'-' read -a line
do
    printf '%s %s\n' ${line[@]}
done < infile > outfile

или ...

# VERSION 2

main(){ 
  local IFS=$'-\n'
  local a=( $( outfile
}

main

или ...

# VERSION 3

while IFS= read -r line
do
  printf '%s\n' "${line/-/ }"
done < infile > outfile

Но будьте осторожны: см. этот пост , если вы думаете применить это к большим файлам.


Ради интереса, несколько тестов для файла среднего размера. Результаты следуют; Очевидно, что лучшим выбором будет 'tr', за ним следует sed, а затем awk. Лучший Bash - это версия 2 (в 625 раз медленнее, чем tr, а использование памяти в 82 раза выше). Для сравнения, sed в 7,5 раз медленнее, а awk в 9 раз медленнее, чем tr.

$ stat -c %s bigdata.txt && wc -l bigdata.txt
1439952
179994 bigdata.txt

# tr '-' $' ' < "$1" > tr.txt

CPU TIME AND RESOURCE USAGE OF './tr bigdata.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :    0.02
CPU, pct :   97.10
RAM, kb  : 1390.00

# sed 's/-/ /g' < "$1" > sed.txt

CPU TIME AND RESOURCE USAGE OF './sed bigdata.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :    0.15
CPU, pct :   98.90
RAM, kb  : 1386.80

# awk 'BEGIN{FS="-"} ; { print $1,$2 }' "$1" > awk.txt

CPU TIME AND RESOURCE USAGE OF './awk bigdata.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :    0.18
CPU, pct :   98.80
RAM, kb  : 1402.00

# BASH: VERSION 1

CPU TIME AND RESOURCE USAGE OF './bash_1 bigdata.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :   16.35
CPU, pct :   99.00
RAM, kb  : 1486.40

# BASH: VERSION 2

CPU TIME AND RESOURCE USAGE OF './bash_2 bigdata.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :   12.51
CPU, pct :   99.40
RAM, kb  : 114002.40

# BASH: VERSION 3

CPU TIME AND RESOURCE USAGE OF './bash_3 bigdata.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :   15.45
CPU, pct :   99.00
RAM, kb  : 1484.00

2
15.10.2018, 15:11
2 ответа

В базовом sed диапазоны выражений в квадратных скобках соответствуют Posix. В Posix диапазоны выражений в квадратных скобках следуют порядку сортировки. Порядок сортировки определяется на основе числового значения символа только в локали C. Но только для однобайтовых значений. Остальные локали не определены в Posix.

Чтобы заставить диапазон работать в выражении скобок sed, нам нужно использовать порядок сортировки, который сортируется по числовой кодовой точке Unicode, это C.UTF -8. Но это создает вторичное требование кодирования символов диапазона в utf8:

  • Получить символьное восьмеричное представление диапазона кодовых точек Unicode (, если используется локаль utf -8):

    $ printf '\u452\u490' | od -An -to1
    

    Если не используется локаль utf -8, преобразовать значения в utf -8:

    $ printf '\u452\u490' | iconv -t utf-8 | od -An -to1
    321 222 322 220
    
  • Добавьте тире и \o, чтобы он работал в старой/настоящей версии sed:

    $ printf '\o%s\o%s-\o%s\o%s' $(printf '\u452\u490'|iconv -tutf-8|od -An -to1)
    \o321\o222-\o322\o220
    
  • Использовать этот диапазон можно в sed:

    $ echo "$a" | LC_ALL=C.UTF-8 sed 's/[^\o321\o222-\o322\o220]//g'
    
  • Но убедитесь, что локаль C.UTF -8 и что заданная строка закодирована в utf8, и преобразуйте ее обратно в используемую локаль:

    $ echo "$a" | iconv -t utf-8 |
                  LC_ALL=C.UTF-8 sed 's/[^\o321\o222-\o322\o220]//g' |
                                    iconv -f utf-8
    

    Обратите внимание , что выше мы использовали оболочку для преобразования \u452\u490.

GNU awk может генерировать строку символов с учетом шестнадцатеричного кода Unicode (при условии, что действующая локаль разрешает такие символы):

<<<"$a" awk 'BEGIN{for(i=0x452;i<=0x490;i++){r=r sprintf("%c", i)}}
 {gsub("[^" range "]", "")}1'

Если текущая локаль не содержит эти точки кода Unicode -в кодовой точке Unicode, вам необходимо преобразовать локаль в локаль, которая, как известно, содержит такие точки кода -и использовать соответствующую переменную среды локали, что-то как:

<<<"$a" iconv -t utf8 |  
LC_ALL=en_US.UTF-8 awk '
        BEGIN{for(i=0x452;i<=0x490;i++){r=r sprintf("%c", i)}}
        {gsub("[^" r "]", "")}1
        ' | iconv -f utf8

Итог либо более высокая оболочка (GNU bash или zsh )либо awk (требуется только GNU ).

Или используйте язык более высокого уровня, например perl:

$ echo "$a" | perl -Mopen=locale -ane 's/[^\x{452}-\x{490}]//g; print'
0
27.01.2020, 22:02

Согласно POSIX, диапазоны выражений в квадратных скобках указываются только на основе кодовой точки в локали C/POSIX. В других регионах это не указано и часто зависит от порядка сортировки, как вы узнали. Вы обнаружите, что в некоторых локалях, в зависимости от инструмента, [g-j], например, включает i, но также и ı, ǵ, иногда даже Iили даже ch, как в некоторых чешских локалях.

zsh— один из тех редких, чьи диапазоны [x-y]основаны на кодовой точке независимо от локали. Для однобайтовых наборов символов -это будет основано на значении байта, для многобайтовых -байтовых — на кодовой точке Unicode или на том, что система использует для представления широких символов внутри с mbstowc()и ко. API (обычно Unicode ).

Так в zsh,

  • [[ $char = [$'\u452'-$'\u490'] ]]
  • [[ $char = [^ђ-Ґ] ]]
  • y=${x//[^ђ-Ґ]/}

будет работать в вашем случае для сопоставления символов в этом диапазоне Unicode при условии, что кодировка локали является многобайтовой -и имеет эти два символа. Существуют однобайтовые наборы символов -, которые содержат некоторые из этих символов (, например ISO8859 -5, в котором большинство из них находятся в U+0401..U+045F ), но в локалях, которые их используют, диапазоны [ђ-Ґ]будут основываться на кодовой точке байтового значения (в соответствующем наборе символов, а не на кодовой точке Unicode ).

В локали C диапазоны основаны на кодовой точке, но набор символов в локали C гарантированно включает только символы из переносимого набора символов , который представляет собой лишь несколько символов, необходимых для написания POSIX. или код C (, ни один из которых не написан кириллицей ). Также гарантируется одиночный -байт , поэтому он не может включать все символы, указанные в Unicode. На практике чаще всего это ASCII.

На практике вы не можете установить LC_COLLATEна C, не установив также LC_CTYPEна C (или, по крайней мере, локаль с одной -байтовой кодировкой ). Однако многие системы имеют локаль C.UTF-8, которую вы можете использовать здесь.

UTF -8 — это один из тех наборов символов, которые могут представлять все символы Unicode, а значит, и все символы любой кодировки. Так что вы могли бы сделать:

< file iconv -t utf-8 |
  LC_ALL=C.UTF-8 sh -c 'sed "$(printf "s/[^\321\222-\322\220]//g")"' |
  iconv -f utf-8

Первое iconvпреобразование из кодировки локали пользователя в UTF -8, \321\222и \322\220представляет собой кодировку UTF -8 U+0452 и U+0490 соответственно, второе iconvпреобразование обратно в кодировку локали.

Если текущая локаль уже использует UTF -8, так как кодировка (и fileзаписываются с использованием этой кодировки ), это можно упростить до:

<file LC_ALL=C.UTF-8 sed 's/[^ђ-Ґ]//g'

или:

<file LC_ALL=C.UTF-8 sed "$(printf "s/[^\321\222-\322\220]//g")"

С помощью GNU sedи при условии, что $POSIXLY_CORRECTне находится в среде, вы можете указывать символы на основе значения байтов их кодировки.

<file LC_ALL=C.UTF-8 sed 's/[^\321\222-\322\220]//g'

Хотя в старых версиях вам может понадобиться:

<file LC_ALL=C.UTF-8 sed 's/[^\o321\o222-\o322\o220]//g'

Или шестнадцатеричный вариант:

<file LC_ALL=C.UTF-8 sed 's/[^\xd1\x92-\xd2\x90]//g'

Другой вариант,для локалей, использующих многобайтовый набор символов -, который включает эти символы в системах, где широкоформатное представление символов основано на Unicode, следует использовать GNU awkи:

awk 'BEGIN{for (i = 0x452; i<=0x490; i++) range = range sprintf("%c", i)}
     {gsub("[^" range "]", ""); print}'

(Первоначально я считал, что POSIX требует, чтобы реализации awk вели себя как GNU awk, но это не так, поскольку POSIX оставляет поведение sprintf("%c", i)неопределенным для значений i, которые не соответствуют кодировке .(не кодовая точка )символа в локали. Это означает, что его нельзя использовать переносимо для многобайтовых -символов ).

В любом случае обратите внимание, что диапазон U+0400.. U+052F — не единственные символы Unicode в кириллице , не говоря уже о языках, использующих кириллицу в качестве письма. Список символов также зависит от версии Unicode.

В системе, подобной Debian -, вы можете получить их список с помощью:

unicode --max 0 cyrillic

(, что дает 435 различных на Ubuntu 16.04, 444 на Debian sid (, вероятно, использующих другую версию Unicode ).

В perlсм. \p{Block: Cyrillic}, \p{Block: Cyrillic_Ext_A,B,C}, \p{Block: Cyrillic_Supplement}... для сопоставления блоков Unicode и \p{Cyrillic}для сопоставления символов кириллицы (, назначенных в настоящее время в версии Unicode, которая ваша версия perlиспользует (см. perl -MUnicode::UCD -le 'print Unicode::UCD::UnicodeVersion', например )).

Так:

perl -Mopen=locale 's/\P{Cyrillic}//g'
2
27.01.2020, 22:02

Теги

Похожие вопросы