10 REM > CSVSort 0.10
   20 REM Sort CSV file from command line
   30 :
   40 ON ERROR REPORT:PROCClose_All:PRINT" at line ";ERL:END
   50 A$=FNOS_GetEnv:d$=".":s$="/":IFos%>31:d$="\":s$="." ELSE IF(os%AND8):d$="/":s$="."
   60 file$=FNcl("",0):sort$=":"+FNcl("",0)
   70 IF file$="" OR sort$=":":PRINT"Syntax: CSVSort <file> <fields>":A%=INKEY(200):QUIT
   80 up%=OPENIN(file$)
   90 :
  100 A$=GET$#up%:IF A$="":A$=GET$#up%  :REM Read header
  110 max%=-1:REPEAT:max%=max%+1
  120   IF LEFT$(A$,1)="""":A$=MID$(A$,INSTR(A$,"""",2))
  130   A%=INSTR(A$,","):IF A%:A$=MID$(A$,A%+1)
  140 UNTILA%=0                         :REM Count number of fields
  150 :
  160 A%=0:REPEAT                       :REM Count size of first ten records
  170   A$=GET$#up%:IF A$="":A$=GET$#up%
  180   num%=num%+LENA$
  190   A%=A%+1
  200 UNTIL A%=10 OR EOF#up%
  210 num%=EXT#up% DIV (num%/10.5)      :REM Estimate number of records
  220 :
  230 PRINT;num%;" records, ";max%+1;" fields";
  240 IF END+num%*300>HIMEM THEN HIMEM=(END+num%*300) AND -4
  250 REM free1%=HIMEM-END:PRINT'"HEAP=&";~END;"  HIMEM=&";~HIMEM;"  Free space=";free1%;" bytes";
  260 DIM head$(max%),line$(max%),data$(num%,max%)
  270 :
  280 PRINT'"Reading...";
  290 PTR#up%=0:PROCcsv_rd(up%,head$()) :REM Read headers
  300 record%=0
  310 REPEAT                            :REM Read the data
  320   PROCcsv_rd(up%,line$())
  330   FOR A%=0 TO max%:data$(record%,A%)=line$(A%):NEXT A%
  340   REM PRINTrecord%;" ";line$(1);" ";line$(2)
  350   record%=record%+1
  360 UNTIL EOF#up% OR record%>num%:CLOSE#up%:up%=0
  370 IF record%>num%:PRINT '"Bad estimate of number of records":A%=INKEY(200):QUIT
  380 num%=record%-1
  390 REM free2%=HIMEM-END:PRINT'"HEAP=&";~END;"  HIMEM=&";~HIMEM;"  Used space=";free1%-free2%;" bytes";
  400 REM PRINT'"Used ";(free1%-free2%)/num%;" bytes per record"
  410 :
  420 PRINT'"Sorting...";
  430 REPEAT                            :REM Sort the data
  440   A%=FNinstrR(sort$,":"):field%=VALMID$(sort$,A%+1):sort$=LEFT$(sort$,A%-1)
  450   isanum%=(STR$VALdata$(1,field%)=data$(1,field%))
  460   PRINT'" by field ";field%;": ";head$(field%);
  470   PROCsort(0,num%,field%,isanum%)
  480 UNTIL sort$=""
  490 :
  500 PRINT'"Saving...";
  510 up%=OPENOUT(file$)                :REM Write the data
  520 PROCcsv_wr(up%,head$())
  530 FOR record%=0 TO num%-1
  540   FOR A%=0 TO max%:line$(A%)=data$(record%,A%):NEXT A%
  550   PROCcsv_wr(up%,line$())
  560 NEXT record%
  570 CLOSE#up%:up%=0:PRINT'"Done"
  580 QUIT
  590 :
  600 DEFPROCClose_All
  610 up%=up%:IFup%:A%=up%:up%=0:CLOSE#A%
  620 ENDPROC
  630 :
  640 DEFPROCcsv_rd(i%,array$())
  650 LOCAL n%:n%=0:array$()="":A$=GET$#i%:IFA$="":A$=GET$#i%
  660 A$=A$+","
  670 REPEAT
  680   IF LEFT$(A$,2)="=""":A$=MID$(A$,2)
  690   IF LEFT$(A$,1)="""" THEN
  700     A%=INSTR(A$,""",",2)+1:array$(n%)=MID$(A$,2,A%-3)
  710   ELSE
  720     A%=INSTR(A$,","):array$(n%)=LEFT$(A$,A%-1)
  730   ENDIF
  740   A$=MID$(A$,A%+1):n%=n%+1
  750 UNTILA$=""
  760 ENDPROC
  770 :
  780 DEFPROCcsv_wr(o%,array$())
  790 LOCAL n%,q%:n%=DIM(array$(),1)
  800 FOR A%=0 TO n%:A$=array$(A%)
  810   q%=INSTR(A$,",")
  820   IF q%=0:q%=(ASCA$=48)AND(INSTR(A$,"/")=0)     :REM leading zeros 00001
  830   IF q%=0:IFVALLEFT$(A$,1):q%=INSTR(A$,"E")     :REM preserve 1234E5678
  840   IF q%=0:q%=LENSTR$VALA$>8                     :REM long numbers 12345678901234
  850   IF q%=0:IFVALA$:q%=INSTR(A$,"/")AND(ASCA$<>48):REM fractions 12/34
  860   IF q%=0:q%=LEFT$(A$,1)="-"                    :REM leading hyphen -
  870   IF q%=0:q%=MID$(A$,3,1)=" "ANDMID$(A$,7,1)=" ":REM dates xx XXX xxxx
  880   IF q%:A$=""""+A$+"""":IFINSTR(A$,",")=0:A$="="+A$
  890   BPUT#o%,A$;:IF A%<>n%:BPUT#o%,",";
  900 NEXT A%:BPUT#o%,""
  910 ENDPROC
  920 :
  930 DEFPROCsort(start%,end%,field%,isanum%)
  940 LOCAL i%,j%,n%,m%:n%=end%
  950 REPEAT:m%=start%
  960   FOR i%=1 TO n%-1
  970     IF isanum% THEN
  980       IF VALdata$(i%-1,field%)>VALdata$(i%,field%):PROCswap(i%-1,i%):m%=i%
  990     ELSE
 1000       IF data$(i%-1,field%)>data$(i%,field%):PROCswap(i%-1,i%):m%=i%
 1010     ENDIF
 1020   NEXT i%:n%=m%
 1030 UNTIL n%=0
 1040 ENDPROC
 1050 :
 1060 DEFPROCswap(i%,j%):FOR A%=0 TO max%:SWAP data$(i%,A%),data$(j%,A%):NEXT A%:ENDPROC
 1070 :
 1080 DEFFNinstrR(A$,B$):A%=LENA$+1:REPEATA%=A%-1:UNTILMID$(A$,A%,LENB$)=B$ OR A%=0:=A%
 1090 :
 1100 DEFFNOS_GetEnv:A%=0:X%=1:os%=((USR&FFF4)AND&FF00)DIV256
 1110 IFPAGE>&FFFFF:DIMX%LOCAL256:SYS"GetModuleFileName",0,X%,255:run$=$$X%:=@cmd$
 1120 A%=(HIMEM>&FFFF)AND&900:A%=((PAGE>&9FFF)ANDA%)OR((&1400-PAGE)AND(A%=0)):IF?(TOP-3)ELSEA%=&B00
 1130 A$=$(PAGE-&E00+A%):IFA%=0:run$=A$:SYS16TOA$,,A%:SYS72,"",A%:A$=MID$(A$,1+INSTR(A$+" "," ",1+INSTR(A$," "))):IFLENA$=0:A$=run$
 1140 FORY%=-1TO0:A$=" "+A$:REPEATA$=MID$(A$,2):UNTILASCA$<>32
 1150   IFY%:IFASCA$=34:A%=INSTR(A$,"""",2)+1 ELSE IFY%:A%=INSTR(A$+" "," ")
 1160   IFY%:run$=MID$(A$,1-(ASCA$=34),A%-1+2*(ASCA$=34)):IFrun$<>"":A$=MID$(A$,A%+1)
 1170 NEXT:=A$
 1180 :
 1190 DEFFNcl(l$,n%):IFl$="":A$=FNs(A$):IFASCA$=34:A%=INSTR(A$+" "" ",""" ",2):l$=MID$(A$,2,A%-2):A$=FNs(MID$(A$,A%+1)):=l$
 1200 IFl$="":A%=INSTR(A$+" "," "):l$=LEFT$(A$,A%-1):A$=FNs(MID$(A$,A%+1)):=l$
 1210 IFn%=0:IFl$<>"":A%=INSTR(A$,l$):IFA%:A$=FNs(LEFT$(A$,A%-1)+MID$(A$,INSTR(A$," ",A%)+1))+" ":=TRUE
 1220 IFn%=0:IFl$<>"":=FALSE
 1230 A%=INSTR(LEFT$(" ",ASCl$=32)+A$,l$):IFA%=0:=""
 1240 A$=LEFT$(A$,A%-1)+FNs(MID$(A$,INSTR(A$," ",A%)+1))
 1250 IFASCl$=32:l$=MID$(A$,A%):A$=LEFT$(A$,A%-1):=MID$(l$,1-(ASCl$=34),LENl$+2*(ASCl$=34))
 1260 IFASCMID$(A$,A%,1)<>34:l$=MID$(A$,A%,INSTR(A$+" "," ",A%)-A%):A$=LEFT$(A$,A%-1)+MID$(A$,A%+LENl$+1):=l$
 1270 l$=MID$(A$,A%+1,INSTR(A$+""" ",""" ",A%+1)-A%-1):A$=LEFT$(A$,A%-1)+MID$(A$,A%+LENl$+3):=l$
 1280 DEFFNs(A$):IFLEFT$(A$,1)=" ":REPEATA$=MID$(A$,2):UNTILLEFT$(A$,1)<>" "
 1290 IFRIGHT$(A$,1)=" ":REPEATA$=LEFT$(A$,LENA$-1):UNTILRIGHT$(A$,1)<>" "
 1300 =A$