извлечь каждый n-й символ из строки

извлечь каждый n-й символ из строки

Я пытаюсь найти решение дляэтотвопрос. Мой подход к этой проблеме на данный момент следующий.

  • Объедините все символы вместе, чтобы получилась длинная строка.
  • После выполнения вышеуказанного шага удалите все пробелы и символы табуляции, чтобы у нас осталась одна большая строка.

Мне удалось выполнить вышеуказанные шаги с помощью следующей команды.

column -s '\t' inputfile | tr -d '[:space:]'

Итак, для такого входного файла,

1   0   0   0   0   0

0   1   1   1   0   0

После применения вышеуказанной команды у меня будут такие значения:

100000011100

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

Извлечь каждый 6 символ (как того требует исходный OP) и добавить его к элементу массива до конца строки.

Итак, по сути, с помощью вышеуказанного шага я пытаюсь создать элементы массива следующим образом:

10(1 и 7 символ), 01(2 и 8 символ), 01(3 и 9 символ), 01(4 и 10 символ), 00(5 и 11 символ), 00(6- й и 12 символ).

Итак, мой вопрос: как мне извлечь каждый n- й символ, чтобы добавить их в массив и продолжить работу? (в данном случае n=6).

решение1

Две линии

Вот чистое 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, через ${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

решение2

С использованием 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

решение3

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

-thisisonelongstringconsistingofseparatecommandlineswitches

Да, getoptsдолжен справиться с этим. Он должен разделить этот символ за символом в цикле и вернуть вам каждый символ либо в переменной оболочки $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

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

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все равно помещает текущее значение opt char в, $OPTARGдаже если он возвращает вам ошибку(представлено знаком ?, назначенным указанной вами переменной opt)и в противном случае явно сбрасывает, $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()для каждого байта и вместо этого назначаю его вывод - который приходит в переменной - другой переменной оболочки - как я сделал выше с setafter way. Затем, когда я готов, я могу взять всю строку, и когда я это делаю, я обычно удаляю первый байт.

решение4

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

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

Соберите 5 персонажей, поймайте шестого и замените их всех этим пойманным персонажем.

Однако это может привести к проблеме, если длина строки не кратна 6:

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

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

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

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

Связанный контент