Eu tenho um arquivo enorme separado por tabulação formatado assim
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11
Gostaria de transpô- lo de uma forma eficiente usando apenas comandos bash (eu poderia escrever um script Perl de dez ou mais linhas para fazer isso, mas deve ser mais lento para executar do que as funções bash nativas). Portanto, a saída deve ser semelhante a
X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11
Eu pensei em uma solução como esta
cols=`head -n 1 input | wc -w`
for (( i=1; i <= $cols; i++))
do cut -f $i input | tr $'\n' $'\t' | sed -e "s/\t$/\n/g" >> output
done
Mas é lento e não parece a solução mais eficiente. Eu vi uma solução para o vi neste post , mas ainda está lento demais. Quaisquer pensamentos / sugestões / ideias brilhantes? :-)
Respostas:
resultado
Desempenho em relação à solução Perl de Jonathan em um arquivo de 10.000 linhas
EDIT por Ed Morton (@ ghostdog74 sinta-se à vontade para deletar se você desaprovar).
Talvez esta versão com alguns nomes de variáveis mais explícitos ajude a responder algumas das perguntas abaixo e, de modo geral, esclareça o que o script está fazendo. Ele também usa as guias como o separador que o OP havia originalmente solicitado para lidar com os campos vazios e, por coincidência, aprimora um pouco a saída para este caso específico.
As soluções acima funcionarão em qualquer awk (exceto awk antigo e quebrado, é claro - há YMMV).
As soluções acima leem todo o arquivo para a memória - se os arquivos de entrada forem muito grandes para isso, você pode fazer o seguinte:
que quase não usa memória, mas lê o arquivo de entrada uma vez por número de campos em uma linha, portanto, será muito mais lento do que a versão que lê todo o arquivo na memória. Ele também assume que o número de campos é o mesmo em cada linha e usa GNU awk para
ENDFILE
e,ARGIND
mas qualquer awk pode fazer o mesmo com testes emFNR==1
eEND
.fonte
Outra opção é usar
rs
:-c
altera o separador da coluna de entrada,-C
altera o separador da coluna de saída e-T
transpõe linhas e colunas. Não use em-t
vez de-T
, porque ele usa um número calculado automaticamente de linhas e colunas que geralmente não é correto.rs
, que tem o nome da função reshape em APL, vem com BSDs e OS X, mas deve estar disponível em gerenciadores de pacotes em outras plataformas.Uma segunda opção é usar Ruby:
Uma terceira opção é usar
jq
:jq -R .
imprime cada linha de entrada como um literal de string JSON,-s
(--slurp
) cria um array para as linhas de entrada depois de analisar cada linha como JSON e-r
(--raw-output
) produz o conteúdo de strings em vez de literais de string JSON. O/
operador está sobrecarregado para dividir strings.fonte
rs
- obrigado pelo ponteiro! (O link é para o Debian; o upstream parece ser mirbsd.org/MirOS/dist/mir/rs )rs
que vem com o OS X,-c
sozinho define o separador de coluna de entrada para uma guia.$'\t'
TTC TTA TTC TTC TTT
, executandors -c' ' -C' ' -T < rows.seq > cols.seq
dárs: no memory: Cannot allocate memory
. Este é um sistema rodando FreeBSD 11.0-RELEASE com 32 GB de RAM. Então, meu palpite é que issors
coloca tudo na RAM, o que é bom para velocidade, mas não para dados grandes.Uma solução Python:
O acima é baseado no seguinte:
Este código assume que cada linha tem o mesmo número de colunas (nenhum preenchimento é executado).
fonte
l.split()
porl.strip().split()
(Python 2.7), caso contrário, a última linha da saída ficará prejudicada. Funciona para separadores de coluna arbitrários, usel.strip().split(sep)
esep.join(c)
se o seu separador estiver armazenado na variávelsep
.o projeto transpose no sourceforge é um programa C semelhante ao coreutil para exatamente isso.
fonte
-b
e-f
.BASH puro, nenhum processo adicional. Um bom exercício:
fonte
printf "%s\t" "${array[$COUNTER]}"
Dê uma olhada no GNU datamash que pode ser usado como
datamash transpose
. Uma versão futura também suportará tabulação cruzada (tabelas dinâmicas)fonte
Aqui está um script Perl moderadamente sólido para fazer o trabalho. Existem muitas analogias estruturais com a
awk
solução de @ ghostdog74 .Com o tamanho dos dados de amostra, a diferença de desempenho entre perl e awk era insignificante (1 milissegundo de um total de 7). Com um conjunto de dados maior (matriz 100x100, entradas de 6 a 8 caracteres cada), perl teve desempenho ligeiramente superior ao awk - 0,026s vs 0,042s. Provavelmente, nenhum dos dois será um problema.
Temporizações representativas para Perl 5.10.1 (32 bits) vs awk (versão 20040207 quando fornecido '-V') vs gawk 3.1.7 (32 bits) no MacOS X 10.5.8 em um arquivo contendo 10.000 linhas com 5 colunas por linha:
Observe que o gawk é muito mais rápido do que o awk nesta máquina, mas ainda mais lento do que o perl. Claramente, sua milhagem irá variar.
fonte
Se você
sc
instalou, você pode fazer:fonte
sc
nomeia suas colunas como um ou uma combinação de dois caracteres. O limite é26 + 26^2 = 702
.Existe um utilitário desenvolvido especificamente para isso,
Utilitário GNU datamash
Retirado deste site, https://www.gnu.org/software/datamash/ e http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods
fonte
Supondo que todas as suas linhas tenham o mesmo número de campos, este programa awk resolve o problema:
Em palavras, conforme você percorre as linhas, para cada campo
f
cresce uma string separada por ':'col[f]
contendo os elementos daquele campo. Depois de terminar com todas as linhas, imprima cada uma dessas strings em uma linha separada. Você pode então substituir ':' pelo separador que deseja (digamos, um espaço) canalizando a saídatr ':' ' '
.Exemplo:
fonte
GNU datamash é perfeitamente adequado para este problema com apenas uma linha de código e tamanho de arquivo potencialmente arbitrariamente grande!
fonte
Uma solução perl hackish pode ser assim. É bom porque não carrega todos os arquivos na memória, imprime arquivos temporários intermediários e, em seguida, usa a pasta maravilhosa
fonte
A única melhoria que posso ver em seu próprio exemplo é usar o awk, que reduzirá o número de processos executados e a quantidade de dados canalizados entre eles:
fonte
Eu normalmente uso este pequeno
awk
snippet para este requisito:Isso apenas carrega todos os dados em uma matriz bidimensional
a[line,column]
e, em seguida, imprime de volta comoa[column,line]
, de modo que transpõe a entrada fornecida.Isso precisa manter o controle da
max
quantidade máxima de colunas que o arquivo inicial possui, de modo que seja usado como o número de linhas a serem impressas de volta.fonte
Usei a solução do fgm (obrigado fgm!), Mas precisava eliminar os caracteres de tabulação no final de cada linha, então modifiquei o script assim:
fonte
Eu estava apenas procurando por uma base bash semelhante, mas com suporte para preenchimento. Aqui está o script que escrevi com base na solução da fgm, que parece funcionar. Se puder ajudar ...
fonte
Eu estava procurando uma solução para transpor qualquer tipo de matriz (nxn ou mxn) com qualquer tipo de dado (números ou dados) e consegui a seguinte solução:
fonte
Se você quiser apenas pegar uma única linha $ N (delimitada por vírgulas) de um arquivo e transformá-la em uma coluna:
fonte
Não é muito elegante, mas este comando de "linha única" resolve o problema rapidamente:
Aqui, cols é o número de colunas, onde você pode substituir 4 por
head -n 1 input | wc -w
.fonte
Outra
awk
solução e entrada limitada com o tamanho da memória que você tem.Isso une cada posição do mesmo número de arquivo e
END
imprime o resultado que seria a primeira linha na primeira coluna, a segunda linha na segunda coluna, etc.fonte
Alguns * nix padrão util one-liners, nenhum arquivo temporário necessário. NB: o OP queria uma solução eficiente (ou seja, mais rápida), e as principais respostas geralmente são mais rápidas do que esta resposta. Esses one-liners são para aqueles que gostam de ferramentas de software * nix , por qualquer motivo. Em casos raros ( por exemplo, IO e memória escassos), esses trechos podem ser mais rápidos do que algumas das principais respostas.
Chame o arquivo de entrada de foo .
Se soubermos que foo tem quatro colunas:
Se não soubermos quantas colunas foo tem:
xargs
tem um limite de tamanho e, portanto, tornaria o trabalho incompleto com um arquivo longo. O limite de tamanho depende do sistema, por exemplo:tr
&echo
:... ou se o número de colunas for desconhecido:
Usando
set
, que, assimxargs
, tem limitações baseadas no tamanho da linha de comando semelhantes:fonte
awk
.cut
,head
,echo
, Etc. não são mais POSIX código shell compatíveis do que umawk
script é - todos eles são padrão em cada instalação UNIX. Simplesmente não há razão para usar um conjunto de ferramentas que, em combinação, exigem que você tome cuidado com o conteúdo do seu arquivo de entrada e com o diretório de execução do script, quando você pode apenas usar o awk e o resultado final é mais rápido e mais robusto .for f in cut head xargs seq awk ; do wc -c $(which $f) ; done
quando o armazenamento é muito lento ou o IO é muito baixo, intérpretes maiores tornam as coisas piores, não importa o quão bons seriam em circunstâncias mais ideais. Razão # 2: awk , (ou qualquer linguagem), também sofre de uma curva de aprendizado mais íngreme do que um pequeno utilitário projetado para fazer bem uma coisa. Quando o tempo de execução é mais barato do que horas de trabalho do codificador, a codificação fácil com "ferramentas de software" economiza dinheiro.outra versão com
set
eval
fonte
Outra variante do bash
Roteiro
Resultado
fonte
Aqui está uma solução Haskell. Quando compilado com -O2, ele é executado um pouco mais rápido do que o awk do ghostdog e um pouco mais lento do que o
cpythoncde Stephan em minha máquina para linhas de entrada "Hello world" repetidas. Infelizmente, o suporte do GHC para a passagem de código de linha de comando é inexistente, pelo que eu posso dizer, então você terá que escrevê-lo em um arquivo. Isso truncará as linhas no comprimento da linha mais curta.fonte
Uma solução awk que armazena todo o array na memória
Mas podemos "percorrer" o arquivo quantas vezes forem necessárias as linhas de saída:
Que (para uma contagem baixa de linhas de saída é mais rápido do que o código anterior).
fonte
Aqui está um one-liner do Bash que se baseia na simples conversão de cada linha em uma coluna e
paste
juntá-las:m.txt:
cria o
tmp1
arquivo para que não fique vazio.lê cada linha e a transforma em uma coluna usando
tr
cola a nova coluna no
tmp1
arquivoo resultado das cópias de volta para
tmp1
.PS: Eu realmente queria usar descritores io, mas não consegui fazê-los funcionar.
fonte
Um oneliner usando R ...
fonte
Eu usei a seguir dois scripts para fazer operações semelhantes antes. O primeiro está em awk, que é muito mais rápido do que o segundo, em bash "puro". Você pode ser capaz de adaptá-lo ao seu próprio aplicativo.
fonte