extrai cada nésimo caractere de uma string

6

Estou tentando descobrir uma solução para this < pergunta. Minha abordagem para este problema até agora é como abaixo.

  • Anexe todos os caracteres juntos para torná-lo uma string longa.
  • Após a etapa acima, remova todos os espaços em branco ou espaços de tabulação para que tenhamos apenas uma string grande.

Consegui estabelecer as etapas acima com o comando abaixo.

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

Então, para um arquivo de entrada como este,

1   0   0   0   0   0

0   1   1   1   0   0

Depois de aplicar o comando acima, eu tenho os valores como

100000011100

Agora, nesta grande string, estou tentando aplicar uma abordagem como abaixo.

Extraia cada caractere th (como o OP original deseja) e acrescente-o a um elemento de matriz até o final da string.

Então, basicamente, com a etapa acima, estou tentando criar os elementos da matriz como

10 (1 st e 7 th caractere), 01 (2 nd e 8 th caractere), 01 (3 rd e 9 th caractere), 01 (4 th e 10 th caractere), 00 (5 th e 11 th caractere), 00 (6 th e 12 th personagem) .

Então, minha pergunta é: como eu poderia extrair cada caractere n th para que eu pudesse adicioná-los a uma matriz para prosseguir? (n = 6, neste caso).

    
por Ramesh 14.10.2014 / 06:22

4 respostas

5

Duas linhas

Aqui está uma solução pure- bash que produz uma matriz bash :

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

Isso produz a mesma saída, conforme mostrado na pergunta:

10 01 01 01 00 00

O elemento chave aqui é o uso da expansão de substring do bash . Bash permite a substrings de extração de uma variável, digamos parameter , via ${parameter:offset:length} . Em nosso caso, o deslocamento é determinado pela variável de loop i e o comprimento é sempre 1 .

Solução geral para qualquer número de linhas

Suponha, por exemplo, que nossa string original tenha 18 caracteres e que desejemos extrair os caracteres i-th, i + 6-th e i + 12-th para i de 0 a 5. Então:

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[@]}"

Isso produz a saída:

102 013 014 015 006 007

Esse mesmo código se estende a um número arbitrário de linhas de 6 caracteres. Por exemplo, se s tiver três linhas (18 caracteres):

s="100000011100234567abcdef"

Em seguida, a saída se torna:

102a 013b 014c 015d 006e 007f
    
por 14.10.2014 / 06:54
4

Usando 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

Funciona por duas linhas. Se você quiser trabalhar com linhas arbitrárias, você deve processar as linhas diretamente, em vez de construir uma string grande. Com esta entrada:

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

Tente:

$ 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
    
por 14.10.2014 / 06:32
3

Como uma solução shell, getopts é provavelmente mais fácil. A coisa sobre getopts é que é especificado em POSIX para fazer exatamente o que você está pedindo - processar um byte-stream em um loop de shell. Eu sei que soa estranho, porque, se você é como eu antes de eu aprender isso, você provavelmente está pensando, bem, puxa, eu pensei que era para lidar com opções de linha de comando. verdade, mas assim é a primeira coisa. Considere:

-thisisonelongstringconsistingofseparatecommandlineswitches

Sim, getopts precisa lidar com isso. Ele tem que dividir esse caractere por char em um loop e retornar a você cada caractere na variável de shell $OPTARG ou em outro que você especificar por nome, dependendo de quão específico você é quando você o chama. Além do mais, ele precisa retornar erros nas variáveis do shell e salvar seu progresso quando isso ocorrer na variável do shell $OPTIND , de modo que ele possa retomar exatamente de onde parou se você pode de alguma forma resolver isso. E tem que fazer todo o trabalho sem invocar uma única subcamada.

Então, digamos que temos:

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

Hmmm .... eu me pergunto se funcionou?

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

Isso é legal ...

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

Então, como você pode ver, o comando getopts definiu completamente a matriz para cada sexto byte na string. E não tem que ser números como este - nem deve ser até mesmo caracteres seguros para shell - e você não precisa nem especificar os caracteres de destino como eu fiz acima com 01234565789 . Eu testei isso repetidamente em muitos shells e todos eles simplesmente funcionam. Existem algumas peculiaridades - bash irá jogar fora o primeiro caractere se ele for um caractere de espaço em branco - dash aceita o : colon como um parâmetro especificado, embora seja apenas o único POSIX que proíbe especificamente. Mas nada disso importa porque getopts ainda deposita o valor atual do char do opt em $OPTARG mesmo quando ele retorna um erro (representado por um? Atribuído ao seu var opt especificado) e caso contrário, explicitamente $OPTARG a menos que você tenha declarado uma opção deve ter um argumento. E o espaço em branco é uma coisa boa - ele só descarta um espaço leading , o que é excelente, porque, ao trabalhar com valores desconhecidos, você pode fazer:

getopts : o -" $unknown_value"

... para iniciar o loop sem qualquer perigo de o primeiro caractere estar realmente em sua sequência de args aceita - o que resultaria em getopts colocando a coisa toda em $OPTARG de uma só vez - como um argumento.

Aqui está outro exemplo:

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

-thisisonelongstringconsistingofseparatecommandlineswitches
4050
arg=$(seq -s '' 1000); set --
while getopts :0123456789 v -"${arg}"
do [ "$((i=$i+1<6?$i+1:0))" -gt 0 ] ||
set "$@" "$v"
done
716520
echo "$((${#arg}/6))" "$#"
482 482
7061
eval '
printf %.1s\n "${arg#'"$(printf %0$((124*6-1))d | tr 0 \?)"'}" "${124}"'
4
4
647415
getopts : o -" $unknown_value"
1215227146
OPTIND=1
while getopts : o -" $(dd if=/dev/urandom bs=16 count=1 2>/dev/null)"                         
do printf '\%04o' "'$OPTARG"; done  

%pre%4050%pre%716520%pre%7061%pre%647415%pre%1215227146%pre%5766
5766

Eu defini $OPTIND=1 na primeira linha porque usei apenas getopts e, até você redefini-la, espera que a próxima chamada continue de onde parou. Em outras palavras, ela quer "${arg2}" . Mas eu não sinto vontade de dar e estou fazendo uma coisa diferente agora, então eu deixo saber, redefinindo $OPTIND em que ponto é bom ir.

Neste eu usei zsh - que não fala sobre um espaço principal - e então o primeiro caractere é octal 40 - o caractere de espaço. Eu normalmente não uso getopts dessa forma - normalmente eu o uso para evitar fazer um write() para cada byte e em vez disso atribuir sua saída - que vem em uma variável - para outra variável de shell - como eu fiz acima com set depois de uma moda. Então, quando eu estiver pronto, posso pegar a string inteira e quando eu costumo tirar o primeiro byte.

    
por 15.10.2014 / 06:13
1

sed é a primeira coisa que me vem à mente.

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

Combine 5 caracteres, capture o sexto e substitua todos eles por esse caractere capturado.

Isso, no entanto, terá um problema se o comprimento da string não for um múltiplo exato de 6:

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

Mas podemos corrigir isso alterando o sed um pouco:

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

Devido à natureza gananciosa da regex, as correspondências de tamanho variável corresponderão ao máximo que puderem, e se não houver mais nada para a captura, ela não será capturada e os caracteres serão excluídos.

    
por 14.10.2014 / 06:33