извлеките каждый энный символ из строки

Просто сделайте:

awk 'NF != 31 || /[^0-9 -]/ {print FILENAME ":" FNR ": " $0}' file1 file2...

Сообщить о строках, которые не имеют 31 поля или имеют нецифры. Не столь строгий как решение @manatwork, поскольку это не лаяло бы на --- или 9-8 например, но это могло бы быть более эффективно.

6
13.04.2017, 15:36
4 ответа

Две строки

Вот чисто-bash решение, которое производит массив bash:

s="100000011100"
array=($(
    for ((i=0; i<${#s}-6; i++))
    do
        echo "${s:$i:1}${s:$((i+6)):1}"
    done
    ))
echo "${array[@]}"

Это производит тот же вывод, который показан в вопросе:

10 01 01 01 00 00

Ключевым элементом здесь является использование расширения подстроки bash bash. Бэш позволяет извлекать подстроки из переменной, скажем параметр, через ${parameter:offset:length}. В нашем случае смещение определяется переменной цикла i, а длина всегда 1.

Общее решение для любого количества строк

Предположим, например, что наша исходная строка имеет 18 символов, и мы хотим извлечь i-ые, i+6-ые и i+12-ые символы для i от 0 до 5. Затем:

s="100000011100234567"
array=($(
    for ((i=0; i<6; i++))
    do
        new=${s:$i:1}
        for ((j=i+6; j<${#s}; j=j+6))
        do 
            new="$new${s:$j:1}"
        done
        echo "$new"
    done
    ))

echo "${array[@]}"

В результате получится:

102 013 014 015 006 007

Этот же код распространяется на произвольное количество 6-символьных строк. Например, если s имеет три строки (18 символов):

s="100000011100234567abcdef"

Затем выдается:

102a 013b 014c 015d 006e 007f
5
27.01.2020, 20:23

sed - это первое, что приходит мне в голову.

$ echo 1234567890abcdefghijklmnopqrstuvwxyz | sed 's/.\{5\}\(.\)/\1/g'
6bhntz

Сопоставьте 5 символов, захватите 6-й, и замените все на этот захваченный символ.

Однако, это будет иметь проблему, если длина строки не будет точно кратна 6:

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{5\}\(.\)/\1/g' 
6bhntuvwxy

Но мы можем исправить это, немного изменив sed:

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{1,5\}\(.\{0,1\}\)/\1/g'
6bhnt

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

1
27.01.2020, 20:23

Используя perl:

$ echo 100000011100 | perl -nle '
    for ($i = 0; $i < length()/2; $i++) {
        print substr($_,$i,1), substr($_,$i+6,1);
    }
'
10
01
01
01
00
00

, он работает для двух строк. Если вы хотите работать с произвольным количеством строк, вам следует обрабатывать строки напрямую, вместо того, чтобы строить большую строку. С этим вводом:

1   0   0   0   0   0                                                           
0   1   1   1   0   0                                                           
0   0   0   0   0   0

Попробуйте:

$ perl -anle '
    for ($i = 0; $i <= $#F; $i++) {
      push @{$h{$i}}, $F[$i];
    }
    END {
        print @{$h{$_}} for keys %h;
    }
' file
000
010
000
100
010
010
4
27.01.2020, 20:23

В качестве решения оболочки, getopts , вероятно, проще всего. Дело в том, что getopts - это POSIX-специфическое решение, позволяющее делать именно то, что вы просите - обрабатывать байт-поток в цикле оболочки. Я знаю, что это звучит странно, потому что, если ты такой же, как я, до того, как я узнал это, ты, наверное, думаешь, , ну, блин, я думал, что это должно было работать с переключателями командной строки. Это правда, но это первое, что я думаю. Подумайте:

-thisisonelongstringconsistingofseparatecommandlineswitches

Да, getopts должен справиться с этим. Он должен разделить этот символ на символ char в цикле и вернуть вам каждый символ либо в переменной shell $OPTARG, либо в другой, которую вы указываете по имени, в зависимости от того, насколько конкретно вы получаете, когда вызываете этот символ. Более того, он должен возвращать ошибки в переменных оболочки и сохранять прогресс, когда он делает это в переменной оболочки $OPTIND, чтобы он мог возобновить работу с того места, где он остановился, если вы можете каким-то образом адресовать его. И он должен сделать всю работу, не вызывая ни одной под-оболочки.

Скажем так:

arg=$(seq -s '' 1000); set --
while getopts :0123456789 v -"${arg}"
do [ "$((i=$i+1<6?$i+1:0))" -gt 0 ] ||
set "$@" "$v"
done

Hmmm.... Интересно, сработало ли это?

echo "$((${#arg}/6))" "$#"
482 482

Это мило...

eval '
printf %.1s\\n "${arg#'"$(printf %0$((124*6-1))d | tr 0 \?)"'}" "${124}"'
4
4

Итак, как вы видите, команда getopts полностью устанавливает массив для каждого шестого байта в строке. И это не обязательно должны быть такие числа - и даже не должны быть символы, безопасные для оболочки - и вам даже не нужно указывать целевые символы, как я делал это выше с 01234565789. Я неоднократно тестировал это во многих оболочках, и все они просто работают. Есть некоторые причуды - bash выкинет первый символ, если это символ пробела - dash принимает двоеточие : в качестве заданного параметра, даже если это практически единственный POSIX, который конкретно запрещает. Но ничего из этого не имеет значения, так как getopts все еще вносит текущее значение оптического символа в $OPTARG, даже когда он возвращает вам ошибку (представленную ?, назначенной вашей указанной var опции), и в противном случае явно не устанавливает $OPTARG, если только вы не объявили опцию, которая должна иметь аргумент. А пробельные символы - это вроде как хорошо - они только отбрасывают ведущий пробел, что отлично, потому что при работе с неизвестными значениями можно сделать:

getopts : o -" $unknown_value"

...чтобы запустить цикл без всякой опасности того, что первый символ на самом деле находится в вашей принятой строке аргументов - в результате чего getopts засовывает все это в $OPTARG сразу - в качестве аргумента.

Вот еще один пример:

OPTIND=1
while getopts : o -" $(dd if=/dev/urandom bs=16 count=1 2>/dev/null)"                         
do printf '\\%04o' "'$OPTARG"; done  

\0040\0150\0071\0365\0320\0070\0161\0064\0274\0115\0012\0215\0222\0271\0146\0057\0166

Я установил $OPTIND=1 в первой строке, потому что я только что использовал getopts и, пока вы его не сбросите, он ожидает, что его следующий вызов продолжится с того места, на котором он остановился - он хочет "${arg2}", иными словами. Но мне не хочется отдавать, и я делаю сейчас другую вещь, поэтому я даю ему знать, сбросив $OPTIND, и в этот момент это хорошо.

В этом я использовал zsh - который не рассуждает о ведущем пробеле - и поэтому первый символ - восьмеричный 40 - символ пробела. Обычно я не использую getopts таким образом, хотя - я обычно использую его, чтобы избежать выполнения write() для каждого байта и вместо этого присваивать его вывод - который приходит в переменной - другой переменной оболочки - как я делал это выше с set после моды. Затем, когда я буду готов, я смогу взять всю строку, а когда я обычно удаляю первый байт.

3
27.01.2020, 20:23

Теги

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