Diagonale d'un file

Comment sortiriez-vous la diagonale d'un file?

Par exemple, j'ai un file avec l'intérieur suivant.

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 

La sortie serait censée ressembler à: 1 7 3 9 5 ou quelque chose comme ça.

Je peux sortir une colonne via cut (cut -d "," -f5 filename), mais je ne sais pas quoi écrire pour ne sortir que la diagonale.

awk solution, pas aussi élégante que la solution @don_chrissti, mais fonctionne là où pas un carré.

 awk -F, '{a=a$++n" "}END{print a}' file 

Python et numpy

Les données d'input que nous examinons peuvent être traitées comme une masortingce ou un tableau bidimensionnel. Maintenant, si nous abordons le problème de ce sharepoint vue, il existe plusieurs outils de calcul qui peuvent être utilisés pour manipuler les masortingces. En particulier, le module numpy de Python le permet. Ainsi, nous pourrions utiliser deux choses – la fonction loadtxt() et la diagonal() pour extraire datatables désirées:

 $ python -c 'import sys,numpy;a=numpy.loadtxt(sys.argv[1],dtype=int,delimiter=",");print( a.diagonal() )' input.txt [1 7 3 9 5] 

Maintenant, c'est le gros morceau du travail fait. Pour rendre la sortie jolie, il suffit de convertir datatables obtenues en strings, et de créer une string séparée par des espaces individuels. Ainsi:

 $ 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 

Bien sûr, tout cela n'a pas besoin d'être fait en une seule ligne. Pour des raisons de lisibilité, nous pouvons créer un petit script qui nous permettra également de traiter tous les noms de files donnés en arguments sur la command line:

 #!/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_ssortingng = " ".join( [ str(int(i)) for i in diag ] ) print(result_ssortingng) 
 sed -ne ' y/,/\n/;G;s/\n$// y/\n_/_\n/;:loop /_$/{ s/// s/^[^_]*_// bloop } y/\n_/_\n/;P s/.*//;H ' input.file | paste -sd' ' 

Nous maintenons un ledger sur les champs séparés par des virgules pour sauter dans l'espace de motif dans l'espace de cale.

Le bouclage coupe l'espace du motif des deux extrémités pour arriver à la situation lorsque le plus à gauche est prêt pour l'printing. Nous pouvons imaginer qu'il brûle une bougie des deux extrémités (cependant, le taux de combustion est différent). De l'avant, nous coupons un champ séparé par des virgules, tandis que de la fin, nous lâchons un \n . La gravure continue jusqu'à ce qu'il n'y ait plus de nouvelles lignes de fuite.

Et maintenant l'élément diagonal est assis à l'avant de l'espace de model.

L'artefact y/\n_/_\n/ est de contourner le fait que POSIX sed n'a pas la nouvelle ligne annulée à l'intérieur d'une class de caractères, [^\n] .

Comme dernière étape pour la ligne actuelle, la zone de maintien est incrémentée. La command paste est d'get la sortie sur une seule ligne.


Si tous les champs sont numériques dans votre csv, vous pouvez également utiliser l'extrait suivant de dc . Le tr est de supprimer les virgules car les champs dc sont délimités par des espaces et les nombres négatifs commencent par _ plutôt que –

 tr ',-' ' _' < file | dc -e '[q]sq [s0zlk<a]sa [?z0=qzlk1+dsk<an32ancz0=?]s? 0skl?x[]p' 

On définit 3 macros, q pour quitter quand on a fini, a pour une boucle pour supprimer les éléments de la fin (popping), et ? pour configurer une boucle pour effectuer une lecture orientée vers la ligne et invoquer la macro a , puis imprimer l'élément diagnol maintenant exposé.

 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 ' 

Sortie:

 1 7 3 9 5 
  1. Shell POSIX :

     n=0 while IFS=',' && read x ; do set -- $x ; shift $n ; echo "$1" ; n=$((n+1)) done < inputfile 
  2. bash peut être plus concis:

      n=0; while IFS=',' read -ax ; do echo "${x[((n++))]}" ; done < inputfile 

Notez qu'il est facile d'get des diagonales pour les quatre rotations des données en filtrant vers tac et rev , (ou tac seul – si datatables sont un tableau carré .csv ). Exemples de shell POSIX , mais faites d'abord de nouvelles valeurs d'input asymésortingques:

 seq 25 | paste -d, - - - - - | tee asym_input 

Sortie:

 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 
  1. A \ gauche à droite en diagonale , (la question OP , avec des inputs différentes):

     n=0 while IFS=',' && read x ; do set -- $x ; shift $n ; echo "$1" ; n=$((n+1)) done < asym_input 

    Sortie:

     1 7 13 19 25 
  2. A / diagonale de gauche à droite :

     n=0 tac asym_input | while IFS=',' && read x ; do set -- $x ; shift $n ; echo "$1" ; n=$((n+1)) done 

    Sortie:

     21 17 13 9 5 
  3. A / droite à gauche diagonale :

     n=0 rev asym_input | while IFS=',' && read x ; do set -- $x ; shift $n ; echo "$1" ; n=$((n+1)) done | rev 

    Sortie:

     5 9 13 17 21 
  4. A \ droite à gauche diagonale :

     n=0 tac asym_input | rev | while IFS=',' && read x ; do set -- $x ; shift $n ; echo "$1" ; n=$((n+1)) done | rev 

    Sortie:

     25 19 13 7 1 

Abstrait:

  1. Carré …..: awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("\n")}' file
  2. Rectangular:

     awk -F, ' NR==1{printf($1);next} {printf(" "$(NR>NF?NF:NR))}END{printf("\n")} ' file` 
  3. Autres diagonales:

     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 
  4. Posix qui sélectionne le nombre diagonal et la direction / vs \ . (le code est long, merci de le lire à la fin de ce post).


Détails

Masortingce carrée

Avec awk, la solution la plus élégante est la suivante:

 $ awk -F, '{print $NR}' file 1 7 3 9 5 

Pour avoir une sortie d'une ligne, vous pourriez faire (avec un espace de fuite):

 $ awk -F, -v ORS=" " '{print $NR}' file; echo 1 7 3 9 5 

Si vous devez avoir la sortie sans espaces de fin:

 $ awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("\n")}' file 1 7 3 9 5 

Rectangular

Pour un file avec, par exemple, ceci:

 $ 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 

La solution ci-dessus imprimera des espaces blancs:

 $ awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("|\n")}' file 1 7 3 9 5 | 

Si ce que vous voulez dans ce cas est d'arrêter le traitement, alors vérifier que le nombre d'loggings est plus grand que le nombre de champs peut être une solution (si le nombre de champs change pour chaque ligne):

 $ awk -F, 'NR>NF{exit}{printf(NR==1?$NR:" "$NR)}END{printf("|\n")}' infile 1 7 3 9 5| 

Si vous voulez imprimer le dernier champ de n'importe quelle ligne où NR> NF:

 $ awk -F, 'NR==1{printf($1);next}{printf(" "$(NR>NF?NF:NR))}END{printf("|\n")}' file 1 7 3 9 5 ejoty| 

Autres diagonales

Si l'on a besoin d'une diagonale différente de la diagonale principale, on peut signaler que la variable diag a une valeur différente de 0 (0 est la diagonale principale de ce code):

 $ 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 ejoty 

Notez que la valeur de diag pourrait être négative:

  $ 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 cioty 

Et la diagonale pourrait être comme / au lieu de \ avec plus de maths:

 $ 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 afkpu $ 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 eimqu 

Coque Posix

Avec un file d'input différent:

 $ 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 

Un équivalent de awk dans le shell compatible Posix peut être:

 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=$ab=$bd=$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 "$@" 

qui, avec l'input ci-dessus, fonctionnera comme suit:

 $ ./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 

Le code a été testé dans certaines coquilles et fonctionne bien.

 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 

Il échoue pour zsh (pas dans l'émulation) à la fois parce que zsh ne se divise pas par défaut et parce que la numérotation des arrays commence à 1 (pas à 0). Il a été testé dans csh et tcsh mais cela ne fonctionne pas.
Et il ne devrait pas y travailler (n'utilisez pas csh pour les scripts!).

Les solutions qui fonctionnent de bas en haut doivent être faciles à build en utilisant tac sur l'input.

Voici une autre alternative utilisant awk:

 awk -F, '{printf "%s%s",$NR,NR==NF?ORS:OFS}' file 1 7 3 9 5 

Une solution shell / coupe:

 index=1 while read line do cut -d, -f ${index} <<< "$line" index=$((++index)) done < input