solução awk, não tão elegante quanto a solução @don_chrissti, mas funciona onde não é um quadrado.
awk -F, '{a=a$++n" "}END{print a}' file
Como você produziria a diagonal de um arquivo?
por exemplo, eu tenho um arquivo com o seguinte dentro.
1,2,3,4,5
6,7,8,9,0
1,2,3,4,5
6,7,8,9,0
1,2,3,4,5
A saída deveria se parecer com: 1 7 3 9 5
ou algo assim.
Eu posso produzir uma coluna por meio do cut (cut -d "," -f5 filename), mas não tenho certeza do que escrever para produzir apenas a diagonal.
Os dados de entrada que estamos vendo podem ser tratados como matriz ou matriz bidimensional. Agora, se abordarmos o problema desse ponto de vista, há várias ferramentas computacionais que podem ser usadas para manipular matrizes. Em particular, o módulo numpy
do Python permite isso. Assim, poderíamos usar duas coisas - a função loadtxt()
e diagonal()
para extrair os dados desejados:
$ python -c 'import sys,numpy;a=numpy.loadtxt(sys.argv[1],dtype=int,delimiter=",");print( a.diagonal() )' input.txt
[1 7 3 9 5]
Agora, esta é a grande parte do trabalho realizado. Para tornar a saída bonita, basta converter os dados obtidos em cadeias de caracteres e criar uma cadeia separada por espaços entre as cadeias individuais. Assim:
$ python -c 'import sys,numpy;a=numpy.loadtxt(sys.argv[1],delimiter=",");print(" ".join( [ str(int(i)) for i in a.diagonal() ]))' input.txt
1 7 3 9 5
Claro, tudo isso não precisa ser feito como uma linha. Por uma questão de legibilidade, podemos criar um pequeno script, que também nos permitirá processar todos os nomes de arquivos dados como argumentos na linha de comando:
#!/usr/bin/env python
import sys
import numpy as np
for filename in sys.argv[1:]:
data=np.loadtxt(filename,delimiter=",")
diag = data.diagonal()
result_string = " ".join( [ str(int(i)) for i in diag ] )
print(result_string)
sed -ne '
y/,/\n/;G;s/\n$//
y/\n_/_\n/;:loop
/_$/{
s///
s/^[^_]*_//
bloop
}
y/\n_/_\n/;P
s/.*//;H
' input.file | paste -sd' '
Mantemos um razão nos campos separados por vírgulas para pular no espaço padrão no espaço de espera.
O looping corta o espaço do padrão de ambas as extremidades para chegar à situação quando o lado mais à esquerda está pronto para ser impresso. Podemos imaginar que esteja queimando uma vela de ambas as extremidades (no entanto, a taxa de queima é diferente). A partir da frente, cortamos um campo separado por vírgula, enquanto, do final, soltamos um \n
à direita. A queima continua até que não haja mais linhas novas.
E agora o elemento diagonal está na frente do espaço do padrão.
O artefato y/\n_/_\n/
é para contornar o fato de que POSIX sed
não tem a nova linha negada dentro de uma classe de caracteres, [^\n]
.
Como último passo para a linha atual, a área de espera é incrementada. O comando paste
é para obter a saída em uma única linha.
Se todos os campos forem numéricos no seu csv, você também poderá usar o seguinte snippet de dc
. O tr
é remover as vírgulas, pois os campos dc são delimitados por espaço e números negativos começam com _ em vez de -
tr ',-' ' _' < file | dc -e '[q]sq [s0zlk<a]sa [?z0=qzlk1+dsk<an32ancz0=?]s? 0skl?x[]p'
Definimos 3 macros, q
para desistir quando terminar, a
para um looping para excluir os elementos do final (popping) e ?
para configurar um loop para executar uma leitura orientada por linha e invocando a macro a
e, em seguida, imprimindo o elemento de diagnóstico agora exposto.
tr ... |
dc -e '
# quit macro
[q]sq
# macro to pop elements from stack till they are more than counter k
[s0 zlk<a]sa
# do-while loop for reading lines
[
?z0=q # quit if line read is empty
zlk1+dsk<a # increment the counter k and compare it against NF (z) call a if >
n 32an # print the diagonal element and a space (no newlines)
c z0=? # invoke myself again
]s?
# main
0sk # initialize the counter k
l?x # call the macro ? to start the file read-in loop
[]p # print a trailing newline
'
Saída:
1 7 3 9 5
POSIX shell:
n=0
while IFS=',' && read x ; do
set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
done < inputfile
bash
pode ser mais conciso:
n=0; while IFS=',' read -a x ; do echo "${x[((n++))]}" ; done < inputfile
Observe que é fácil obter diagonais para todas as quatro rotações dos dados, filtrando para tac
e rev
, ou tac
sozinho - se os dados forem um quadrado < em> .csv array). Exemplos de shell POSIX , mas primeiro faça alguns novos valores de entrada assimétricos:
seq 25 | paste -d, - - - - - | tee asym_input
Saída:
1,2,3,4,5
6,7,8,9,10
11,12,13,14,15
16,17,18,19,20
21,22,23,24,25
A \
da esquerda para a direita na diagonal , (a questão OP , com entrada diferente):
n=0
while IFS=',' && read x ; do
set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
done < asym_input
Saída:
1
7
13
19
25
Uma /
da esquerda para a direita na diagonal :
n=0
tac asym_input | while IFS=',' && read x ; do
set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
done
Saída:
21
17
13
9
5
Uma /
da direita para a esquerda na diagonal :
n=0
rev asym_input | while IFS=',' && read x ; do
set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
done | rev
Saída:
5
9
13
17
21
Uma \
da direita para a esquerda na diagonal :
n=0
tac asym_input | rev | while IFS=',' && read x ; do
set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
done | rev
Saída:
25
19
13
7
1
awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("\n")}' file
Retangular:
awk -F, ' NR==1{printf($1);next}
{printf(" "$(NR>NF?NF:NR))}END{printf("\n")}
' file'
Outras diagonais:
awk -F, -vdiag=9 -vdir=-1 '
{d=(NR-1)*(dir>0?1:-1)+1+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
{printf("%s%s",NR==1?"":" ",$d)}
END {printf("\n")}
' file
Posix que seleciona o número diagonal e a direção /
vs \
.
(o código é longo, por favor leia no final deste post).
Com o awk, a solução mais elegante é:
$ awk -F, '{print $NR}' file
1
7
3
9
5
Para ter uma saída de uma linha, você poderia fazer (com um espaço à direita):
$ awk -F, -v ORS=" " '{print $NR}' file; echo
1 7 3 9 5
Se você precisa ter a saída sem espaços à direita:
$ awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("\n")}' file
1 7 3 9 5
Para um arquivo com, por exemplo, isto:
$ cat file
1,2,3,4,5
6,7,8,9,0
1,2,3,4,5
6,7,8,9,0
1,2,3,4,5
a,b,c,d,e
f,g,h,i,j
k,l,m,n,o
p,q,r,s,t
u,v,w,x,y
A solução acima irá imprimir espaços em branco:
$ awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("|\n")}' file
1 7 3 9 5 |
Se o que você deseja, neste caso, é parar o processamento, verificar se o número do registro é maior que o número de campos pode ser uma solução (se o número de campos for alterado para cada linha, isso pode não ser o correto solução):
$ awk -F, 'NR>NF{exit}{printf(NR==1?$NR:" "$NR)}END{printf("|\n")}' infile
1 7 3 9 5|
Se o que você deseja é imprimir o último campo em qualquer linha onde NR > NF:
$ awk -F, 'NR==1{printf($1);next}{printf(" "$(NR>NF?NF:NR))}END{printf("|\n")}' file
1 7 3 9 5 e j o t y|
Se o que é necessário é uma diagonal diferente da "diagonal principal", podemos sinalizar isso configurando a variável diag com um valor diferente de 0 (0 é a diagonal principal neste código):
$ awk -F, -vdiag=3 ' {d=NR+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
{printf("%s%s",NR==1?"":" ",$d)}
END {printf("\n")}
' file
4 0 5 0 5 e j o t y
Observe que o valor de diag pode ser negativo:
$ awk -F, -vdiag=-3 ' {d=NR+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
{printf("%s%s",NR==1?"":" ",$d)}
END {printf("\n")}
' infile
1 6 1 6 2 c i o t y
E a diagonal pode ser igual a /
em vez de \
com mais matemática:
$ awk -F, -vdiag=4 -vdir=-1 '
{d=(NR-1)*(dir>0?1:-1)+1+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
{printf("%s%s",NR==1?"":" ",$d)}
END {printf("\n")}
' file
5 9 3 7 1 a f k p u
$ awk -F, -vdiag=9 -vdir=-1 '
{d=(NR-1)*(dir>0?1:-1)+1+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
{printf("%s%s",NR==1?"":" ",$d)}
END {printf("\n")}
' infile
5 0 5 0 5 e i m q u
Com um arquivo de entrada diferente:
$ printf '%s\n' {1..6}{1..5} 7{1..3} | pr -ta -5 -s',' | tee inputfile
11,12,13,14,15
21,22,23,24,25
31,32,33,34,35
41,42,43,44,45
51,52,53,54,55
61,62,63,64,65
71,72,73
Um equivalente ao awk no shell compatível com Posix pode ser:
diagPosix(){ diag=${1%%[!0-9+-]*} dir=$(((${2:-1}>0)?1:-1)) n=0 a=""
while read x ; do
# echo "testing $n $x"
IFS=',' eval 'set -- $x' # Place values in pos parms.
a=$(( diag + n*dir )) # calculate position a
b=$(( (a<0)?0:a )) # low limit is zero (0)
d=$(( (b>$#-1)?$#-1:b )) # upper limit is ($#-1)
# echo "a=$a b=$b d=$d #=$# n=$n"
shift $d # remove leading parms
printf '%s' "$s" "$1" # print parm (and an space)
s=" " # Next loop will have space.
n=$((n+1)) # In which line are we?
done <"${3:-inputfile}"
echo
}
diagPosix "$@"
que, com a entrada acima, funcionará da seguinte forma:
$ ./script 0 1 inputfile
11 22 33 44 55 65 73
$ ./script -2 1 inputfile
11 21 31 42 53 64 73
$ ./script 4 -1 inputfile
15 24 33 42 51 61 71
O código foi testado em alguns shells e funciona bem.
ash : 11 22 33 44 55 65 73
/usr/bin/yash : 11 22 33 44 55 65 73
y2sh : 11 22 33 44 55 65 73
dash : 11 22 33 44 55 65 73
zsh/sh : 11 22 33 44 55 65 73
b203sh : 11 22 33 44 55 65 73
b204sh : 11 22 33 44 55 65 73
b205sh : 11 22 33 44 55 65 73
b30sh : 11 22 33 44 55 65 73
b32sh : 11 22 33 44 55 65 73
b41sh : 11 22 33 44 55 65 73
b42sh : 11 22 33 44 55 65 73
b43sh : 11 22 33 44 55 65 73
b44sh : 11 22 33 44 55 65 73
lksh : 11 22 33 44 55 65 73
mksh : 11 22 33 44 55 65 73
ksh93 : 11 22 33 44 55 65 73
attsh : 11 22 33 44 55 65 73
zsh/ksh : 11 22 33 44 55 65 73
Ele falha para zsh (não em emulação) porque zsh não é dividido por padrão e porque a numeração de array começa em 1 (não 0).
Foi testado em csh e tcsh mas não funciona.
E é não esperado que funcione lá (não use csh para scripts!).
As soluções que funcionam de baixo para cima devem ser fáceis de construir usando o tac na entrada.
Aqui está outra alternativa usando o awk:
awk -F, '{printf "%s%s",$NR,NR==NF?ORS:OFS}' file
1 7 3 9 5
Uma solução baseada em shell / corte:
index=1
while read line
do
cut -d, -f ${index} <<< "$line"
index=$((++index))
done < input
Tags text-processing