1

I have a text list with the following structure (all the lines on each entry start with a tab space, there are no blank lines between those lines and there is one blank line between entries):

  292G.- La Ilíada (tomo I) ; Collection one (volume 3) ; Homer ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Homero/Iliada.pdf
  - I have to download more ancient greek texts.
  - Another note line.

  293G.- El Ingenioso Hidalgo "Don Quijote" De La Mancha ; Collection one (volume 1) ; Miguel de Cervantes ; http://www.daemcopiapo.cl/Biblioteca/Archivos/7_6253.pdf
  - Masterpiece.

  294G.- Crimen y castigo ; Collection one (volume 4) ; Fiódor Dostoyevski ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Fedor%20Dostoiewski/Crimen%20y%20castigo.pdf
  - Russian masterpiece.

  295G.- La isla del tesoro ; Collection one (volume 2) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - I read this one as a kid.

Going on with over 100 volumes of Collection one starting at position 292G. I wanted those 100 volumes sorted by the volume number (which can be found in the second field). The expected output is:

  292G.- El Ingenioso Hidalgo "Don Quijote" De La Mancha ; Collection one (volume 1) ; Miguel de Cervantes ; http://www.daemcopiapo.cl/Biblioteca/Archivos/7_6253.pdf
  - Masterpiece.

  293G.- La isla del tesoro ; Collection one (volume 2) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - I read this one as a kid.

  294G.- La Ilíada (tomo I) ; Collection one (volume 3) ; Homer ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Homero/Iliada.pdf
  - I have to download more ancient greek texts.
  - Another note line.

  295G.- Crimen y castigo ; Collection one (volume 4) ; Fiódor Dostoyevski ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Fedor%20Dostoiewski/Crimen%20y%20castigo.pdf
  - Russian masterpiece.

Be aware that titles can contain characters and strings such as ", (, ), but not ; (those act only as separators). I suppose sort has the answer here, but this is beyond my rookie skills.

6
  • 1
    why do you deleted "so the primary number (the number with the G) has to be recalculated." part in revision 7 from your question? it was the clarification about how output generates. because you then telling that for given answer unix.stackexchange.com/questions/639814/… – αғsнιη Mar 22 at 10:10
  • 1
    Those first fields look book titles. Book titles can contain characters and strings such as ;, (, ), (volume 3), etc. which would make processing your text much more difficult and probably break all of the answers you have so far. You must be handling that currently (I'd guess by quoting your fields but idk) so please edit your question to include those cases (e.g. 296G - Kill Bill; Bury Him (volume 2) ; Collection one (volume 1) ; Tarantino ; ...) and show how you handle them (296G - "Kill Bill; Bury Him (volume 2)" ; Collection...?) so we see how non-trivial input is formatted. – Ed Morton Mar 22 at 13:38
  • Are there volumes before and/or after "Collection one" volumnes that you do not want sorted but should just remain as-is with the sorted "Collection one" columns replaced in-situ? If so add some of those other collections to your sample input/output. – Ed Morton Mar 22 at 13:47
  • @αғsнιη sorry for the misunderstanding. I want a new G number for the item that is being relocated (as in the example, La isla del tesoro was 293G and now is 295G). I have deleted the expression because I thought that there may be confusion, as in the final output the G numbers are in the same old position globally (first 292G, second 293G, etc...) – fich Mar 25 at 10:51
  • @EdMorton yes, titles may have special characters, I edited that. One exception is ; that only act as a separator. I have indeed volumes after Collection one that I do not want to be sorted, or volumes of Collection one beyond those 100 straight volumes of Collections one, but I think if I include those options in the question, it will be almost intractable – fich Mar 25 at 11:04
1
+100

This (using GNU awk for the 3rd arg to match(), gensub(), sorted_in, and FPAT) will only sort the section you want (i,e. collection "one" with sequence number "292" or greater), can handle titles that contain any characters or strings including ;, (, ) or (volume <N>), and will output the sorted section in its original place within the unsorted surrounding sections:

$ cat tst.awk
BEGIN {
    RS = ""
    ORS = "\n\n"
    FPAT = "[^;]*(\"[^\"]*\")*[^;]*"
    tgtColl = "one"
    begSeqNr = 292
    maxSeqs = 100
}
match($2,/Collection (.*) \(volume ([0-9]+))/,a) {
    coll  = a[1]
    volNr = a[2]
    seqNr = $1+0
}
(coll == tgtColl) && (seqNr >= begSeqNr) && (++seqCnt <= maxSeqs) {
    vols[volNr] = $0
    next
}
{
    prtVols()
    print
}
END { prtVols() }

function prtVols(       volNr, seqNr, vol) {
    PROCINFO["sorted_in"] = "@ind_num_asc"
    seqNr = begSeqNr
    for (volNr in vols) {
        vol = vols[volNr]
        sub(/[0-9]+/,seqNr++,vol)
        print vol
    }
    delete vols
}

For example, given this input modified from the sunny-day case in the question to add a couple of useful test cases:

$ cat file
  100G.- some earlier collection ; Collection zero (volume 1) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - TEST earlier collection ID

  200G.- right collection, too early sequence number; Collection one (volume 6) ; Homer ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Homero/Iliada.pdf
  - TEST earlier sequence number

  292G.- La Ilíada ; Collection one (volume 3) ; Homer ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Homero/Iliada.pdf
  - I have to download more ancient greek texts.
  - Another note line.

  293G.- El Quijote ; Collection one (volume 1) ; Miguel de Cervantes ; http://www.daemcopiapo.cl/Biblioteca/Archivos/7_6253.pdf
  - Masterpiece.

  294G.- Crimen y castigo ; Collection one (volume 4) ; Fiódor Dostoyevski ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Fedor%20Dostoiewski/Crimen%20y%20castigo.pdf
  - Russian masterpiece.

  295G.- "Kill Bill; Bury Him (volume 2)" ; Collection one (volume 5) ; Tarantino ; https://www.biblioteca.org.ar/libros/130864.pdf
  - TEST quoted title with sparator chars and target string

  296G.- La isla del tesoro ; Collection one (volume 2) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - I read this one as a kid.

  300G.- some later collection ; Collection twenty-three (volume 2) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - TEST later collecion ID

it will output:

$ awk -f tst.awk file
  100G.- some earlier collection ; Collection zero (volume 1) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - TEST earlier collection ID

  200G.- right collection, too early sequence number; Collection one (volume 6) ; Homer ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Homero/Iliada.pdf
  - TEST earlier sequence number

  292G.- El Quijote ; Collection one (volume 1) ; Miguel de Cervantes ; http://www.daemcopiapo.cl/Biblioteca/Archivos/7_6253.pdf
  - Masterpiece.

  293G.- La isla del tesoro ; Collection one (volume 2) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - I read this one as a kid.

  294G.- La Ilíada ; Collection one (volume 3) ; Homer ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Homero/Iliada.pdf
  - I have to download more ancient greek texts.
  - Another note line.

  295G.- Crimen y castigo ; Collection one (volume 4) ; Fiódor Dostoyevski ; http://www.ataun.eus/BIBLIOTECAGRATUITA/Cl%C3%A1sicos%20en%20Espa%C3%B1ol/Fedor%20Dostoiewski/Crimen%20y%20castigo.pdf
  - Russian masterpiece.

  296G.- "Kill Bill; Bury Him (volume 2)" ; Collection one (volume 5) ; Tarantino ; https://www.biblioteca.org.ar/libros/130864.pdf
  - TEST quoted title with sparator chars and target string

  300G.- some later collection ; Collection twenty-three (volume 2) ; Robert Louis Stevenson ; https://www.biblioteca.org.ar/libros/130864.pdf
  - TEST later collecion ID

Since it's the field separator, any ; that appears in the title has to be within double quotes, either by itself Kill Bill";" Bury Him or as part of a whole quoted title as in the above example, no other characters or strings in the title need any special treatment.

If you actually want all of collection one and not just starting from a sequence number or vice-versa it's an extremely trivial tweak and obvious to just not test for one or the other and similarly if you want all of collection one sorted starting at the given begSeqNr instead of just 100 of them then just don't include the text for seqCnt, and if you don't want surrounding collections/sequences printed then just get rid of the standalone print statement.

2
  • Thanks @Ed Morton, it worked perfectly. Well, truth be told, almost perfectly. Only 2 errors on the list: one bad sorted, and other in good position but the G number did not change. One of these books is in French with several special characters, so this is a great achievement. – fich Mar 27 at 19:20
  • You're welcome. If you include the failing cases in the sample input/output in your question they may not be difficult to deal with. – Ed Morton Mar 27 at 20:33
3

You did not specify an explicit language requirement so here is a dirty solution in python 3.8. I am sure someone else can come up with something way better but it should be sufficient.

The code assumes that the text is in a file named list.txt in the current directory and will create a new file named new-list.txt

It also does not handle the missing space in "-La isla del tesoro"

import re

booklist = []
bookcount = 0
entry = ''
line_numbers = []

# Find and return the volume number for a book
def get_volnum(book):
        volstring = ''
        volstring = re.search('\\(volume (\d+)\\)', book)
        volnum = volstring.group(1)
        return volnum

# Read file and put in doc variable
doc = open('list.txt', 'r').readlines()

# Group each book in a single string and append in a booklist
for line in doc:
    # if line begins with three decimals followed by 'G.', put line in a new entry. 
    if re.match("(\d\d\d)G.*", line): 
        #read the line number and append to a list
        line_numbers.append(line.split('G.')[0])
        # Add previous entry to booklist (without the three decimals and G.)
        if bookcount > 0:
            booklist.append(entry.split('G.')[1])  

        entry = line
        bookcount +=1
    # If line begins with a '- ', concatenate the line into the current entry.
    if line.startswith('- '):
        entry += line

#Append last line
booklist.append(entry.split('G.')[1])  
# Make a list (booktable) that contains [volnum, book]
booktable = []
[booktable.append([get_volnum(book), book]) for book in booklist]

# Sort that list by volnum (index 0 of each list item of booktable)
booktable.sort(key=lambda x: int(x[0]))

line_numbers.sort()

# Write result to file
f = open("new-list.txt", "w")
for b in booktable:
    f.write(line_numbers.pop(0) + 'G.' + b[1])
    f.write('\n')

f.close()
1
  • Thanks Joce, but maybe do you not have taken into account the tabs there are at the begining of each line. I get an error: File "sort_list.py", line 35, in <module> booklist.append(entry.split('G.')[1]) IndexError: list index out of range – fich Mar 22 at 3:42
1

Via awk and the GNU-feature (!) of defining array traversal. Note: stores the whole file in RAM once, but you said "over 100 volumes" so I assume the file is not incredibly large.

The idea is

  1. separate records by empty lines (two newlines in a row, no TAB assumed)
  2. use parentheses as field separators: get lines into array with volume number as index identifier. Therefore the number needs to be separated out with sub
  3. sort output by "volume X" index
  4. simply replace the numbers (293G etc) for each entry in a sorted manner

Script:

BEGIN { RS="" ; ORS="\n\n" ; FS="[()]" }

{id=$2 ; sub(/volume /,"",id) ; vol[id]=$0}    

END {PROCINFO["sorted_in"]="@ind_num_asc"
    n=292
    for ( id in vol ) { gsub(/^\t.../,"\t"n++,vol[id]) ; print vol[id] } }

Run via

awk -f script inputfile
4
  • 1
    Your solution does sort by volume number, but do not reassigns the primary G number in the new position. – fich Mar 22 at 3:47
  • Make the following edits: in the BEGIN block set RS= to enable paragraph mode. In the END block, set n=292 to match the G number in the data. – guest_7 Mar 22 at 7:36
  • @fich so you want the old G number in the new position? The desired output in your question says otherwise? – FelixJN Mar 22 at 8:35
  • @guest_7 good point with n= ... had used ++n before and forgot updating here. Why not directly suggest the edit? – FelixJN Mar 22 at 8:37
0
<infile awk -F';' -v RS= '
        /Collection one/{ n=$2; gsub(/[^0-9]*/, "", n); sub(/[0-9]+/, 292+n-1) }
        { print sep $0; sep="\0" }' |sort -z |tr '\0' '\n'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.