Reorganizar colunas usando recortar


135

Estou tendo um arquivo no seguinte formato

Coluna1 Coluna2
str1 1
str2 2
str3 3

Quero que as colunas sejam reorganizadas. Eu tentei abaixo do comando

cut -f2,1 file.txt

O comando não reordena as colunas. Alguma idéia de por que não está funcionando?

Obrigado.

Respostas:


148

Para a cut(1)página do manual:

Use um e apenas um de -b, -c ou -f. Cada lista é composta de um intervalo, ou muitos intervalos separados por vírgulas. A entrada selecionada é gravada na mesma ordem em que é lida e é gravada exatamente uma vez.

Ele atinge o campo 1 primeiro, para que seja impresso, seguido pelo campo 2.

Use em awkvez disso:

awk '{ print $2 " " $1}' file.txt

12
Pena cutque não suporta este comando de reordenamento intuitivo. De qualquer forma, outra dica: você pode usar awk's' -FSe -OFSopções para usar separadores de campos de entrada e saída personalizados (como -de --output-delimiterpara cut).
malana

12
Desculpe, FSé uma opção, OFSé uma variável. por exemploawk -v OFS=";" -F"\t" '{print $2,$1}'
malana 29/08

2
Nota aos usuários do Git Bash para Windows: se você tiver uma saída estranha do comando acima, parecendo colunas substituindo uma à outra, o retorno de carro é o culpado. Altere o EOL no seu arquivo de CRLF para LF.
precisa saber é o seguinte

1
Alternativamente, se você não quiser alterar o arquivo de entrada, você pode canalizar através | sed 's/\r//' | antes de tubulação paraawk
jakub.g

2
Este é muito simples, mas pode ser útil para alguns, simplesmente substituir o espaço com \ t para reordenar por abas, e no caso de você querer mais colunas, você pode fazê-lo como por exemploawk '{print $4 "\t" $2 "\t" $6 "\t" $7}' file
FatihSarigol

64

Você também pode combinar cute paste:

paste <(cut -f2 file.txt) <(cut -f1 file.txt)

via comentários: é possível evitar bashisms e remover uma instância de corte fazendo:

paste file.txt file.txt | cut -f2,3

3
Não tenho certeza se isso é qualificado como "habilmente", mas: f = file.txt cole <(corte -f2 $ f) <(corte -f1 $ f). Além disso, observo que esse método é o mais fácil quando você tem muitas colunas e deseja mover grandes blocos delas.
Michael Rusch 22/03

não funciona com células de comprimentos variáveis na mesma coluna
kraymer

2
@kraymer O que você quer dizer? cutfunciona bem para colunas de comprimento variável, desde que você tenha um separador de colunas exclusivo.
tripleee

1
Para eliminar o arquivo redundante, você provavelmente poderia usar o tee:
JJW5432

2
É possível evitar bashismos e remover uma instância do seguinte modo cut: paste file.txt file.txt | cut -f2,3
agc

7

usando apenas a concha,

while read -r col1 col2
do
  echo $col2 $col1
done <"file"

Isso é muitas vezes ineficiente. Normalmente, você encontrará que o script Awk correspondente é muito mais rápido, por exemplo. Você também deve ter cuidado ao citar os valores "$col2"e "$col1"- pode haver metacaracteres de shell ou outras travessuras nos dados.
tripleee

7

Você pode usar o Perl para isso:

perl -ane 'print "$F[1] $F[0]\n"' < file.txt
  • -e opção significa executar o comando depois que
  • -n significa ler linha por linha (abra o arquivo, neste caso STDOUT, e faça um loop sobre linhas)
  • -a significa dividir essas linhas em um vetor chamado @F ("F" - como Field). Vetores de índices Perl começando em 0, diferentemente do corte, que indexa os campos que começam no formulário 1.
  • Você pode adicionar o padrão -F (sem espaço entre -F e o padrão ) para usar o padrão como um separador de campos ao ler o arquivo em vez do espaço em branco padrão

A vantagem de executar o perl é que (se você conhece o Perl), é possível fazer muito mais computação em F do que reorganizar as colunas.


O perlrun (1) afirma -a define implicitamente -n, mas se eu executar sem -n set, ele não parece fazer um loop. ímpar.
Trenton

Qual versão? perl -ae printfunciona como catpara mim
pwes

5

Usando join:

join -t $'\t' -o 1.2,1.1 file.txt file.txt

Notas:

  • -t $'\t'Em GNU join o mais intuitivo -t '\t' , sem a $falhar, ( coreutils v8.28 e anteriores?); provavelmente é um bug que uma solução alternativa $deve ser necessária. Veja: unix join separator char .

  • joinprecisa de dois nomes de arquivos, mesmo que apenas um arquivo esteja sendo trabalhado. Usar o mesmo nome duas vezes engana joinpara executar a ação desejada.

  • Para sistemas com poucos recursos, joinoferece uma área útil menor do que algumas das ferramentas usadas em outras respostas:

    wc -c $(realpath `which cut join sed awk perl`) | head -n -1
      43224 /usr/bin/cut
      47320 /usr/bin/join
     109840 /bin/sed
     658072 /usr/bin/gawk
    2093624 /usr/bin/perl

3

Acabei de trabalhar em algo muito semelhante, não sou especialista, mas pensei em compartilhar os comandos que usei. Eu tinha um csv de várias colunas que exigia apenas 4 colunas e, em seguida, precisava reordená-las.

Meu arquivo era o canal '|' delimitado, mas que pode ser trocado.

LC_ALL=C cut -d$'|' -f1,2,3,8,10 ./file/location.txt | sed -E "s/(.*)\|(.*)\|(.*)\|(.*)\|(.*)/\3\|\5\|\1\|\2\|\4/" > ./newcsv.csv

É certo que é realmente áspero e pronto, mas pode ser ajustado para se adequar!


Isso não responde à pergunta colocada. No espírito de estouro de pilha, dedique um tempo para responder a um problema antes de postar.
Bill Gale

0

Usando sed

Use sed com subexpressões aninhadas da expressão regular básica para capturar e reordenar o conteúdo da coluna. Essa abordagem é mais adequada quando há um número limitado de cortes para reordenar colunas, como neste caso.

A idéia básica é cercar partes interessantes do padrão de pesquisa com \(e \), que podem ser reproduzidas no padrão de substituição com \#where #representa a posição seqüencial da subexpressão no padrão de pesquisa.

Por exemplo:

$ echo "foo bar" | sed "s/\(foo\) \(bar\)/\2 \1/"

rendimentos:

bar foo

O texto fora de uma subexpressão é digitalizado, mas não retido para reprodução na sequência de substituição.

Embora a questão não tenha discutido colunas de largura fixa, discutiremos aqui, pois essa é uma medida digna de qualquer solução apresentada. Para simplificar, vamos assumir que o arquivo é delimitado por espaço, embora a solução possa ser estendida para outros delimitadores.

Espaços em Colapso

Para ilustrar o uso mais simples, vamos supor que vários espaços possam ser recolhidos em espaços únicos e os valores da segunda coluna são finalizados com EOL (e não preenchido com espaço).

Arquivo:

bash-3.2$ cat f
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  nl
0000040    s   t   r   2  sp  sp  sp  sp  sp  sp  sp   2  nl   s   t   r
0000060    3  sp  sp  sp  sp  sp  sp  sp   3  nl 
0000072

Transformar:

bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f
Column2 Column1
1 str1
2 str2
3 str3
bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  nl
0000020    1  sp   s   t   r   1  nl   2  sp   s   t   r   2  nl   3  sp
0000040    s   t   r   3  nl
0000045

Preservando as larguras das colunas

Vamos agora estender o método para um arquivo com colunas de largura constante, permitindo que as colunas tenham larguras diferentes.

Arquivo:

bash-3.2$ cat f2
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f2
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  sp
0000040   sp  sp  sp  sp  sp  nl   s   t   r   2  sp  sp  sp  sp  sp  sp
0000060   sp   2  sp  sp  sp  sp  sp  sp  nl   s   t   r   3  sp  sp  sp
0000100   sp  sp  sp  sp   3  sp  sp  sp  sp  sp  sp  nl
0000114

Transformar:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2
Column2 Column1
1       str1      
2       str2      
3       str3      
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   2  sp  sp  sp  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Por fim, embora o exemplo da pergunta não tenha seqüências de comprimento desigual, essa expressão sed suporta esse caso.

Arquivo:

bash-3.2$ cat f3
Column1    Column2
str1       1      
string2    2      
str3       3      

Transformar:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3
Column2 Column1   
1       str1      
2       string2   
3       str3    
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   i   n   g   2  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Comparação com outros métodos de reordenação de colunas no shell

  • Surpreendentemente, para uma ferramenta de manipulação de arquivos, o awk não é adequado para cortar de um campo para o final do registro. No sed, isso pode ser realizado usando expressões regulares, por exemplo, \(xxx.*$\)onde xxxestá a expressão que corresponde à coluna.

  • O uso de subshells de colar e cortar fica complicado ao implementar scripts shell internos. O código que funciona a partir da linha de comando falha ao analisar quando trazido para dentro de um script de shell. Pelo menos essa foi a minha experiência (o que me levou a essa abordagem).


0

Expandindo a resposta do @Met, também usando Perl:
se a entrada e a saída forem delimitadas por TAB:

perl -F'\t' -lane 'print join "\t", @F[1, 0]' in_file

Se a entrada e a saída forem delimitadas por espaço em branco:

perl -lane 'print join " ", @F[1, 0]' in_file

Aqui,
-ediz ao Perl para procurar o código embutido, em vez de em um arquivo de script separado,
-n lê a linha de entrada 1 por vez,
-lremove o separador de registros de entrada ( \nno * NIX) após ler a linha (semelhante a chomp) e adiciona a saída separador de registros ( \nno * NIX) para cada um print,
-adivide a linha de entrada no espaço em branco na matriz @F,
-F'\t'em combinação com -adivide a linha de entrada nas TABs, em vez de espaço em branco na matriz @F.

@F[1, 0]é a matriz composta pelos segundo e primeiro elementos da matriz @F, nesta ordem. Lembre-se de que as matrizes em Perl são indexadas a zero, enquanto os campos em cutsão indexadas a 1. Portanto, os campos em @F[0, 1]são os mesmos que os campos cut -f1,2.

Observe que essa notação permite uma manipulação mais flexível da entrada do que em algumas outras respostas postadas acima (que são boas para uma tarefa simples). Por exemplo:

# reverses the order of fields:
perl -F'\t' -lane 'print join "\t", reverse @F' in_file

# prints last and first fields only:
perl -F'\t' -lane 'print join "\t", @F[-1, 0]' in_file
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.