Quello che hai visto fino a oggi (PowerShell: spostare file in cartelle per anno e mese (in base al nome) e PowerShell: spostare file in cartelle per anno e mese (in base alla data)) era solo la punta dell’iceberg. Ho adottato entrambi i metodi che ti ho già raccontato per mettere in ordine file tutto sommato facili da gestire e dalla quantità più o meno modesta (circa 500.000). Mi sono però scontrato come il Titanic (per rimanere in tema) contro un nemico evidentemente sottovalutato. Una quantità di file PDF decisamente importante (parliamo di quasi 3 milioni di file) con alcune particolarità difficili da gestire: si tratta di fatture, bolle accompagnatorie, note di accredito e altri documenti simili (dal 2013 a oggi), tenuti in cartelle per anno (di difficile caricamento pure per Windows a causa dell’alta quantità di file), senza nessun riferimento alla data nel nome del file e senza quei metadati originali che mi avrebbero permesso di utilizzare la data di creazione come metodo per riorganizzarle.
Come scavalcare quindi l’ostacolo? Semplice (si fa per dire), si sbircia nel file PDF alla ricerca della data di emissione del documento e si agisce su quella.
PSDataReorg
Documentazione, ispirazione, lurking su GitHub e limitrofi, vale tutto in questo caso – soprattutto considerando che qui in mezzo non sono certo io lo sviluppatore, ma è pur vero che so come arrangiarmi – fino ad arrivare alla giusta soluzione che sono certo qualcuno possa migliorare ma che a oggi funziona e sta facendo il suo mestiere. Con estrema banalità la soluzione si chiama PSDataReorg e ricalca quanto già scritto e pubblicato su GitHub / Gist nelle settimane passate, anche se questa si appoggia su un paio di librerie esterne che mi permettono di ficcare il naso nei file PDF e gestirli portandomi fuori il necessario per la riorganizzazione degli stessi.
Sto parlando di iTextSharp e Bouncy Castle (che serve più che altro a iTextSharp). La prima delle due soluzioni è esattamente ciò che mi è servito per portare a casa il risultato. iTextSharp è in grado di scorrere progressivamente il file PDF rendendolo di fatto una serie di stringhe all’interno delle quali sarà possibile cercare ciò che ti interessa o – banalmente – fermarti a una determinata linea se questa rimane “fissa da template” (nel caso di una fattura o altro documento simile generalmente lo è). Immagina quindi di poter scandagliare il documento e trovare nel percorso la data di emissione del documento, bingo. È proprio quello su cui mi sono basato nel primo caso (bolle accompagnatorie con un testo ben preciso prima della data di emissione, facile quindi creare un pattern per la ricerca), in un secondo caso ho invece avuto la necessità di fissarmi su una riga ben precisa contenente la sola data, difficile quindi da rendere pattern di ricerca senza fare danni (prendere cantonate e altre date che nulla c’entravano con quella di emissione del documento). Io ti rilascio tutto il codice sviluppato e utilizzato perché altri come me abbiano una strada spianata più facile e immediata da intraprendere, la filosofia alla base della condivisione della conoscenza è (e deve rimanere) sempre la stessa.
Prepara il terreno
Tutto quello che ti serve si trova nel mio spazio GitHub, più precisamente all’indirizzo github.com/gioxx/PSDataReorg. Puoi scaricare l’intero repository facendo clic qui. Una volta scompattato ti servirà preparare l’ambiente, necessito infatti di avere a disposizione iTextSharp e Bouncy Castle che posso ottenere direttamente da NuGet. Dovrai quindi avviare un prompt di PowerShell ed eseguire DataReorg_Setup.ps1
. Così facendo verrà scaricato tutto il necessario, creata una cartella Lib
e inserita al suo interno ciò che poi verrà richiamato più e più volte dai miei script di DataReorg.
Questa operazione dovrebbe durare davvero molto poco, dipende tutto dalla tua connessione. Ora sei praticamente pronto per partire con la riorganizzazione, lascia che ti spieghi come “visualizzare” il file PDF di riferimento tramite un rapido debug prima di arrivare a capire come funzionano i due script principali.
Lettura del PDF
Questo passaggio è fondamentale per capire come verrà letto il tuo documento da iTextSharp e – di conseguenza – cosa potrai ricercare e isolare all’interno del PDF. Per tua comodità ho scritto un piccolissimo script di “debug” che troverai nella cartella Tools
. Spostati (via PowerShell) nella cartella Tools
ed esegui .\DataReorg_PDF_Debug.ps1
passandogli come parametro il percorso di uno dei PDF che intendi riorganizzare, nel mio esempio sarà .\DataReorg_PDF_Debug.ps1 C:\temp\PSDataReorg-main\2018-0000000005.pdf
:
Tutto ciò che ti sembrerà maledettamente confuso e impossibile da capire ti sembrerà più chiaro nel momento in cui lo script – al termine della lettura – ti aprirà il file di log generato nel quale ha inserito correttamente ciò che ha letto riga per riga. Io uso Notepad++ su Windows e ti assicuro che così facendo la lettura del file sarà più agile, se il tuo editor di testo predefinito è Blocco Note avrai certamente un problema, passa a qualcosa di migliore (Notepad++ o Atom sono solo due possibili alternative):
Ho così scoperto che in riga 28 (da lettura di iTextSharp) trovo una stringa che contiene il numero progressivo del documento ma – soprattutto – la sua data di emissione. In un solo colpo potrei utilizzare entrambi gli script (quello basato sul pattern di ricerca e quello basato sulla linea precisa da analizzare). Partiamo dal pattern di ricerca, ti spiegherò comunque come funzionano entrambi.
Pattern di ricerca
Ci siamo, siamo pronti a partire. Quello che ci interessa è sapere che la stringa da analizzare e dalla quale estrapolare ciò che ci serve inizia con un “N.ro e data documento“. Ciò che occorrerà manipolare di quella stringa consisterà nella data di emissione del documento che – per standard NON americano – sarà specificata come gg/mm/aaaa
(giorno a due cifre, mese a due cifre, anno a quattro cifre). Lo script per la ricerca basata su un pattern ben preciso è DataReorg_PDF.ps1
. Questo si basa su 2 parametri fondamentali e due opzionali:
- la cartella sorgente all’interno della quale si trovano attualmente i file PDF;
- la cartella di destinazione all’interno della quale creare la struttura per anno e mese (sì, può essere la medesima della sorgente);
- (opzionale) l’anno che si sta esaminando: utile nel caso in cui nella cartella analizzata siano finiti PDF di un diverso anno, lo script a quel punto non creerà solo la cartella del mese ma anche quella dell’anno se l’anno rilevato è differente rispetto a quello analizzato;
- (opzionale)
-Recursive
: è un “interruttore” che dirà allo script di cercare i file PDF nella cartella principale ma anche in tutte le sottocartelle.
Aprendo lo script con l’editor di testo noterai che al suo interno ho specificato l’estensione dei file da analizzare e il pattern preciso da ricercare (riga 35, nda):
$textFilter = "N.ro e data documento" $filesFilter = "*.pdf"
È logico che modificando la variabile $textFilter
con ciò che ti serve potrai adattare lo script sulla tua reale esigenza. Fallo e salva la modifica appena eseguita, torna poi alla finestra di PowerShell, sei pronto a lanciare lo script per riorganizzare i tuoi primi file PDF: .\DataReorg_PDF.ps1 C:\temp\PDF_test\ C:\temp\PDF_test\ 2018
tenterà di analizzare i PDF che si trovano nella cartella C:\temp\PDF_test
riorganizzandoli all’interno della stessa cartella (sorgente e destinazione infatti corrispondono), il tutto a patto che si tratti dell’anno 2018.
Questa è la cartella prima della riorganizzazione:
E questa subito dopo:
Nel frattempo lo script di PowerShell ha svolto il compito per il quale è stato pensato, mostrando a video l’avanzamento e scrivendo all’interno di un file di log (che verrà salvato nella cartella di destinazione e riporterà la data della riorganizzazione seguita dalla posizione della cartella sorgente priva di caratteri speciali che Windows mal digerisce) che al suo interno riporterà le operazioni eseguite, passo-passo:
Il tempo impiegato dallo script per terminare il lavoro è assolutamente variabile. Più file hai, più tempo impiegherai per rimetterli a posto. L’operazione di analisi e spostamento è davvero velocissima ma tutto dipende davvero dall’ambiente in cui lavorerai, se sposterai i file da unità di rete, se il disco risponderà in tempi rapidi, ecc. Per spostare circa 100.000 file da disco locale a medesima destinazione ho impiegato poco più di due minuti, facendolo da rete (con sorgente un vecchio NAS stanco in una località remota rispetto alla destinazione) ci ho messo molto più tempo, spostando circa 1 o 2 file al secondo, cavandomela così in circa 30 minuti di attesa).
Dietro le quinte lo script avrà cercato – per ciascun file PDF analizzato – la stringa che comincia per $textFilter
, quindi “N.ro e data documento” e l’avrà:
- pulita da spazi di troppo (
if ($line.Contains($textFilter)) { $DateRow = $line.Trim() }
, vedi devblogs.microsoft.com/scripting/trim-your-strings-with-powershell); - ottenuto esclusivamente gli ultimi 10 caratteri che corrispondono alla data
gg/mm/aaa
($CleanDate = $DateRow.Substring($DateRow.get_Length()-10)
), togliendo successivamente da quei 10 caratteri gli slash ($CleanDate = $CleanDate -replace '/',''
) e finalmente ottenuto la data pulita separando tutto in variabili dedicate ($year = $CleanDate.Substring(4,4); $month = $CleanDate.Substring(2,2); $day = $CleanDate.Substring(0,2)
); - se stai pensando che avrei potuto evitare di eliminare gli slash dalla data beh, hai ragione, ma chissenefrega, ho voluto fare una cosa pulita;
A questo punto effettuerà la verifica della cartella di destinazione/anno/mese (a patto che questa esista, diversamente la creerà $Directory = $Destination + $year + "\" + $month
o – in alternativa se si specifica l’anno – if ($YearFilter -eq $year) { $Directory = $Destination + $month } else { $Directory = $Destination + $year + "\" + $month }
) e sposterà infine il file ($file | Move-Item -Destination $Directory
).
Con iTextSharp tutto questo diventa possibile ed è quindi davvero semplice far fare allo script ciò che si vuole.
Riga precisa da analizzare
Questo script è nato successivamente perché analizzando un diverso tipo di documento PDF ho scoperto che la stringa che riportava il progressivo e la data di emissione era isolata da tutto il resto. Lanciando infatti il debug del documento PDF (come spiegato due paragrafi più sopra) ho ottenuto questo file di log:
Curioso che stavolta “N.ro e data” si trovi nella riga subito successiva, motivo per il quale lo script originale basato sul pattern di ricerca falliva lo spostamento perché beh, sì, la stringa riusciva a trovarla, peccato solo non avere alcuna data da analizzare :-)
A questo punto ho analizzato un importante campione di quei documenti rilevando che la data si trovava sempre in riga 11 nel file di log post-debug da lettura di iTextSharp, ho quindi assunto che quella riga sarebbe potuta diventare il mio nuovo riferimento. Una piccola modifica allo script vedrà al posto di $textFilter
(con il precedente “N.ro e data documento“) una variabile differente, più precisamente $dateLine = 10
dove 10 è il numero della riga da analizzare se si parte a contare da 0 anziché da 1. Il resto è rimasto grosso modo inalterato, cambierà chiaramente il modo di catturare la stringa che mi serve per lo spostamento (dal precedente if ($line.Contains($textFilter)) { $DateRow = $line.Trim() }
al necessario if ($lineIndex -eq $dateLine) { $DateRow = $line.Trim() }
). Cambia quel valore con ciò che serve a te per adattare lo script alle tue reali esigenze e salva il file PowerShell.
Il resto fa completamente riferimento allo script originale (puoi quindi leggere quello per capire il funzionamento dello script “LineBased“). Basterà perciò lanciare un .\DataReorg_PDF_LineBased.ps1 C:\temp\PDF_test\LineBased\ C:\temp\PDF_test\LineBased\ 2016
(riferito al mio specifico esempio) per dare inizio alle danze, ti propongo le stesse schermate di prima con la cartella pre-riorganizzazione, post-riorganizzazione e cosa è successo nel frattempo:
Inutile che ti mostri il file di log prodotto dallo script, è praticamente identico a quello che ti ho già mostrato nel paragrafo precedente e ti dirà cosa ha fatto per ogni file analizzato così da ricostruire l’esatto andamento dell’operazione appena terminata.
In conclusione
Convinto che si sia potuto fare di meglio e che si possa certamente rendere parametri da riga di comando anche i pattern di ricerca o il numero di riga da prendere in esame posso solo dirti che a oggi questi due script hanno spostato con successo circa 2 milioni di file, il loro lavoro non è terminato e ne sto approfittando per riorganizzare almeno un altro milione di file per poi finire nella cassetta degli attrezzi fino a prossimo compito da assolvere. Questa necessità mi ha permesso di scoprire iTextSharp e giocare con PowerShell per automatizzare un’operazione diversamente non affrontabile, sono molto contento del risultato raggiunto ma sono assolutamente aperto a ogni tuo suggerimento o miglioramento, il codice è completamente disponibile su GitHub, divertiti.
Prima di chiudere sul serio l’articolo ti anticipo che in caso di ingenti spostamenti di file (che superano i 10.000, da quanto ho potuto notare) noterai un messaggio a video da parte di iTextSharp il quale ti farà presente che quel software ha una licenza AGPL alla base e che tu dovrai pubblicare il tuo codice con la medesima licenza per poter richiedere la rimozione di quel messaggio dall’error log, altrimenti potrai acquistare una licenza commerciale e tenere il tuo codice tutto per te:
Questo è il testo dell’errore che ti verrà mostrato da iTextSharp:You are using iText under the AGPL.
If this is your intention, you have published your own source code as AGPL software too.
Please let us know where to find your source code by sending a mail to agpl@itextpdf.com
We’d be honored to add it to our list of AGPL projects built on top of iText or iTextSharp and we’ll explain how to remove this message from your error logs.
If this wasn’t your intention, you are probably using iText in a non-free environment.
In this case, please contact us by filling out this form: http://itextpdf.com/sales
If you are a customer, we’ll explain how to install your license key to avoid this message.
If you’re not a customer, we’ll explain the benefits of becoming a customer.
Ciò che credo sia fondamentale sapere è che nulla di tutto questo inficerà sul corretto funzionamento di PSDataReorg che continuerà a fare i suoi movimenti nonostante a video compariranno molti di quei messaggi di “allerta” (nella finestra di PowerShell). Io dal canto mio contatterò il team di iTextSharp per far conoscere il mio codice e chiedere loro di eliminare quel messaggio, aggiornerò non appena possibile questo articolo :-)
A questo punto ho davvero concluso un articolo biblico dedicato evidentemente agli addetti ai lavori più che ai lettori generici fedeli di questo blog. Io sono a tua totale disposizione nell’area commenti in caso di dubbi o ulteriori informazioni.
#StaySafe
Immagine di copertina: Viktor Talashuk on Unsplash
L'articolo potrebbe non essere aggiornato
Questo post è stato scritto più di 5 mesi fa, potrebbe non essere aggiornato. Per qualsiasi dubbio ti invito a lasciare un commento per chiedere ulteriori informazioni! :-)