`sys.argv` dentro `main()`: argomenti da terminale senza argparse
Come leggere gli argomenti da terminale con sys.argv dentro main(): controllo, conversione in numeri, virgolette e quando passare ad argparse.
Hai uno script con main() e la guardia if __name__ == "__main__":, come nella guida Capire if __name__ == "__main__" con esempi, e vuoi che accetti un valore scritto dopo il nome del file: un nome, un numero, un percorso. Leggi sys.argv[1] e funziona, finché una volta lanci lo script senza niente dopo il nome e Python risponde con IndexError: list index out of range. sys.argv è una lista di stringhe: il primo elemento è il nome dello script, gli argomenti veri partono da argv[1]. Leggila dentro main(), controlla quanti ne sono arrivati e converti i numeri da solo, perché per Python è tutto testo.
La risposta breve è lo script ripeti.py più sotto. Prima ti mostro che cosa contiene la lista e dove si rompe, con comandi eseguiti su Debian con Python 3.13.5 e output copiati come sono usciti.
ℹ️ Nota / Prerequisito operativo: serve conoscere il pattern
main()più guardia della guida madre e un terminale aperto in una cartella vuota, che chiamolab-argv. Su Windows usapythonal posto dipython3; i comandi sono quelli che ho eseguito su Debian, e su Windows e macOS non li ho lanciati. I percorsi assoluti negli output (per esempio/home/box/lab-argv) sono quelli della mia macchina di prova: sul tuo computer saranno diversi.
Il laboratorio: uno script che legge il primo argomento
Crea nella cartella lab-argv il file saluta_argv.py. Legge sys.argv[1] dentro main() e saluta quella persona:
import sys
def main():
nome = sys.argv[1]
print(f"Ciao, {nome}!")
if __name__ == "__main__":
main()
Dalla cartella lab-argv, scrivi un nome dopo il nome del file. Su Windows:
python saluta_argv.py Alex
Su macOS e Linux:
python3 saluta_argv.py Alex
Ciao, Alex!
Ora lancialo senza nessun argomento: python saluta_argv.py su Windows, python3 saluta_argv.py su macOS e Linux. Nella prova ha risposto così:
Traceback (most recent call last):
File "/home/box/lab-argv/saluta_argv.py", line 10, in <module>
main()
~~~~^^
File "/home/box/lab-argv/saluta_argv.py", line 5, in main
nome = sys.argv[1]
~~~~~~~~^^^
IndexError: list index out of range
Il codice è corretto, ma manca il controllo: con zero argomenti la lista ha un solo elemento e l’indice 1 non esiste.
Cosa c’è dentro sys.argv (e perché argv[0] è il nome dello script)
sys.argv è la lista che Python riempie all’avvio con quello che hai scritto sulla riga di comando, a parte le opzioni dell’interprete. Secondo la documentazione ufficiale, argv[0] è il nome dello script (se sia un percorso completo dipende dal sistema operativo) e gli altri elementi sono gli argomenti, nell’ordine in cui li hai scritti. Per guardarla in faccia ho usato mostra_argv.py:
import sys
def main():
print("argv completo:", sys.argv)
print("argv[0], il nome dello script:", sys.argv[0])
print("argomenti veri, argv[1:]:", sys.argv[1:])
print("quanti:", len(sys.argv) - 1)
if __name__ == "__main__":
main()
Lancialo con due argomenti. Su Windows:
python mostra_argv.py uno due
Su macOS e Linux:
python3 mostra_argv.py uno due
argv completo: ['mostra_argv.py', 'uno', 'due']
argv[0], il nome dello script: mostra_argv.py
argomenti veri, argv[1:]: ['uno', 'due']
quanti: 2
La fetta sys.argv[1:] è la lista degli argomenti veri, e len(sys.argv) - 1 ti dice quanti sono. Con --nome Alex -v ho ottenuto ['mostra_argv.py', '--nome', 'Alex', '-v']: dopo il nome dello script, i trattini non hanno nessun significato speciale per Python, e --nome è una stringa come le altre.
sys.argv ha un solo elemento e l’indice 1 dà IndexError; con mostra_argv.py si vede la lista completa. Screenshot: ai riparoIl valore di argv[0] cambia con il modo in cui avvii il file. Nella prova:
Come lo avvii (da lab-argv) |
argv[0] nella prova |
Come leggerlo |
|---|---|---|
python3 mostra_argv.py x |
mostra_argv.py |
il nome come l'hai scritto |
python3 ./mostra_argv.py x |
./mostra_argv.py |
anche il ./ fa parte del valore |
python3 -m mostra_argv x |
/home/box/lab-argv/mostra_argv.py |
con -m è il percorso completo del file |
python3 -c 'import sys; print(sys.argv)' uno due |
-c |
nessun file: Python ci mette -c |
Gli argomenti che ti interessano stanno in sys.argv[1:]; argv[0] serve al massimo per un messaggio.
Lo script completo: ripeti.py controlla gli argomenti
Questo è ripeti.py: saluta una persona una o più volte. Accetta NOME e, facoltativo, VOLTE. Lo mostro intero; le sezioni dopo lo smontano un pezzo alla volta:
import sys
USO = "Uso: python3 ripeti.py NOME [VOLTE]"
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
if not 1 <= len(argv) <= 2:
print(USO, file=sys.stderr)
return 2
nome = argv[0]
volte = 1
if len(argv) == 2:
try:
volte = int(argv[1])
except ValueError:
print(f"VOLTE deve essere un numero intero, non {argv[1]!r}", file=sys.stderr)
return 2
if volte < 1:
print("VOLTE deve essere almeno 1", file=sys.stderr)
return 2
for _ in range(volte):
print(f"Ciao, {nome}!")
return 0
if __name__ == "__main__":
sys.exit(main())
Provalo con un nome e poi con un nome e un numero. Su Windows:
python ripeti.py Alex 3
Su macOS e Linux:
python3 ripeti.py Alex 3
Ciao, Alex!
Ciao, Alex!
Ciao, Alex!
Ora il caso che prima rompeva tutto: nessun argomento. Dopo il comando leggo anche il codice di uscita, cioè il numero che il programma restituisce al terminale. Su Windows, nel Prompt dei comandi:
python ripeti.py
echo %ERRORLEVEL%
In PowerShell il codice si legge con $LASTEXITCODE. Su macOS e Linux:
python3 ripeti.py; echo "codice di uscita: $?"
Uso: python3 ripeti.py NOME [VOLTE]
codice di uscita: 2
Al posto del traceback c’è un messaggio d’uso. Lo ottengono tre righe: if not 1 <= len(argv) <= 2 accetta solo uno o due argomenti; print(USO, file=sys.stderr) scrive il messaggio sull’uscita degli errori, separata da quella normale; return 2 passa il codice a sys.exit(main()), l’ultima riga del file, che lo consegna al terminale. Nella documentazione di sys.exit si legge che i programmi Unix usano in genere 2 per gli errori di sintassi della riga di comando e 1 per gli altri errori. Ho anche controllato che python3 ripeti.py > uscita.txt lasci il messaggio sul terminale e crei un file vuoto: stderr non finisce nel file.
Gli argomenti sono testo: convertire i numeri
Dal terminale arrivano solo stringhe: 3 arriva come '3', e lo devi convertire con int(). Se l’utente scrive qualcosa che non è un numero, int() solleva un ValueError; in ripeti.py lo intercetto e lo trasformo in un messaggio chiaro. Nella prova:
python3 ripeti.py Alex tre
VOLTE deve essere un numero intero, non 'tre'
La !r dentro la f-string mostra il valore con le virgolette, così si vedono subito spazi o errori di battitura. Con 0 risponde VOLTE deve essere almeno 1: senza il controllo volte < 1, range(0) non farebbe nemmeno un giro e lo script finirebbe in silenzio.
💡 Consiglio pratico — Converti all’ingresso: leggi, converti e controlla gli argomenti nelle prime righe di
main(). Il resto della funzione lavora con valori già validi.
Spazi, virgolette e caratteri jolly: cosa arriva a Python
Prima che Python parta, è il terminale (la shell) a spezzare la riga negli spazi. Python riceve già la lista fatta. Lanciando ripeti.py con un nome e cognome senza virgolette:
python3 ripeti.py Mario Rossi
VOLTE deve essere un numero intero, non 'Rossi'
Mario è diventato NOME e Rossi è finito in VOLTE. Per tenere insieme le due parole servono le virgolette; ho usato le doppie:
python3 ripeti.py "Mario Rossi" 2
Ciao, Mario Rossi!
Ciao, Mario Rossi!
Su Windows non ho provato questo comando.
I caratteri jolly sono un altro caso. Con due file prova_uno.txt e prova_due.txt nella cartella, python3 mostra_argv.py prova_*.txt ha stampato ['mostra_argv.py', 'prova_due.txt', 'prova_uno.txt']: nella prova la shell (bash) ha sostituito il modello con i nomi dei file, e a Python sono arrivati due argomenti. Con le virgolette singole, 'prova_*.txt', è arrivata la stringa letta alla lettera ['mostra_argv.py', 'prova_*.txt']. Se il tuo script deve accettare un modello e comportarsi allo stesso modo ovunque, passalo tra virgolette ed espandilo tu con il modulo glob; su Windows e macOS non ho provato come si comporta la shell.
Perché main(argv=None) e non sys.argv ovunque
sys.argv appartiene al processo, cioè a chi ha lanciato Python, e non al singolo file. Se un file lo legge al primo livello, in cima, ogni programma che importa quel file per riusare una funzione eredita anche i suoi argomenti. Per vederlo ho scritto letto_in_cima.py, che ripete l’errore della prima sezione fuori da main():
import sys
nome = sys.argv[1]
print("letto_in_cima.py ha letto:", repr(nome))
e usa_letto_in_cima.py, che lo importa e basta:
import letto_in_cima
print("usa_letto_in_cima.py: import completato")
Lanciato con python3 usa_letto_in_cima.py Mario, il modulo importato ha letto 'Mario', cioè l’argomento di chi lo importa. Lanciato senza argomenti, è terminato con un IndexError già durante l’import, prima ancora che usa_letto_in_cima.py facesse qualcosa.
ripeti.py evita il problema in due modi: legge sys.argv[1:] solo dentro main(), e main accetta una lista come parametro. usa_ripeti.py la sfrutta:
import ripeti
codice = ripeti.main(["Lettore", "2"])
print("main() ha restituito", codice)
Ciao, Lettore!
Ciao, Lettore!
main() ha restituito 0
Anche lanciandolo con python3 usa_ripeti.py uno due tre, l’output è stato lo stesso: main(["Lettore", "2"]) usa la lista che gli passi e ignora sys.argv. Così puoi provare lo script da un altro file senza aprire un terminale, e il codice di uscita diventa un valore da controllare.
main() con una lista esplicita non dipende dagli argomenti del terminale; un modulo che legge sys.argv in cima li eredita da chi lo importa. Screenshot: ai riparo⚠️ Attenzione — Un
importnon deve leggere gli argomenti: sesys.argvviene letto fuori damain(), un sempliceimportpuò fallire o comportarsi in modo diverso a seconda di chi lancia il programma. Il pattern è: leggisys.argv[1:]in un punto solo, passa i valori amain()e lascia che a chiamarla sia la guardia.
Quando sys.argv basta e quando passare ad argparse
Per uno o due argomenti in posizione fissa sys.argv basta; con le opzioni a trattino no, perché non le distingue dagli altri valori. Con python3 ripeti.py --help, ripeti.py ha risposto Ciao, --help!: per lui è un nome come un altro. Il modulo argparse della libreria standard fa il lavoro che qui dovresti scrivere a mano: opzioni, valori predefiniti, conversione dei tipi e un --help generato. Nel sorgente di Python 3.13.5 ho verificato che argparse legge proprio sys.argv[1:] (la riga è args = _sys.argv[1:]), e che con un argomento obbligatorio mancante esce con codice 2, come ripeti.py.
| Situazione | sys.argv a mano basta? |
Perché |
|---|---|---|
| Un argomento obbligatorio un nome, un percorso | Sì | Basta len(argv) e un messaggio d'uso, come in ripeti.py. |
Un numero facoltativo in posizione fissa
come VOLTE
|
Dipende | Funziona finché l'ordine non cambia; ogni controllo in più è codice tuo. |
Opzioni come --verbose
in ordine libero
|
No | La lista non distingue opzioni e valori: --help è arrivato come testo. |
Aiuto automatico e tipi
valori predefiniti, --help
|
No | È il lavoro di argparse: la guida agli argomenti CLI con argparse parte da qui. |
I due pezzi main(argv=None) e sys.exit(main()) restano utili anche con argparse: al posto dei controlli a mano ci metti un parser, e sys.argv[1:] lo legge lui.
Errori tipici
IndexError: list index out of rangesusys.argv[1]→ hai lanciato lo script senza argomenti e il codice legge l’indice senza controllare → verificalen(argv)all’inizio dimain(), stampa l’uso sustderre restituisci 2.ValueError: invalid literal for int() with base 10: 'tre'→ hai passato testo aint()senza intercettare l’errore (nella prova è il messaggio cheint('tre')produce da solo) → racchiudi la conversione intry/except ValueErrore scrivi un messaggio che dica quale argomento è sbagliato.- Il secondo argomento sembra sbagliato quando c’è uno spazio → nome e cognome senza virgolette diventano due argomenti (
'Rossi'è finito inVOLTE) → metti il valore tra virgolette doppie. IndexErrorappena importi il file → qualcuno leggesys.argvfuori damain()e l’import lo esegue → sposta la lettura dentromain()e lascia asys.exit(main())il compito di chiamarla.- Un messaggio d’uso con
-co un percorso lungo → hai usatoargv[0], che cambia con il modo in cui avvii il file → scrivi il nome del file a mano nella stringaUSO.
Domande Frequenti (FAQ)
Che cos’è sys.argv in Python?
È la lista di stringhe con quello che hai scritto dopo python3, a parte le opzioni dell’interprete. Il primo elemento è il nome dello script, e gli altri sono gli argomenti nell’ordine in cui li hai scritti.
Perché sys.argv[0] non è il primo argomento?
Perché Python ci mette il nome dello script, come descrive la documentazione. Nella prova valeva mostra_argv.py, ./mostra_argv.py o il percorso completo con -m; per questo gli argomenti veri sono sys.argv[1:].
Come passo un argomento che contiene spazi?
Mettilo tra virgolette doppie: python3 ripeti.py "Mario Rossi" 2 ha dato un solo NOME. Senza virgolette la shell spezza la riga negli spazi.
Devo leggere sys.argv dentro main() o in cima al file?
Dentro main(), o meglio ancora in un punto solo che poi passa la lista a main(argv). Nella prova, un file che lo legge in cima ha dato IndexError durante un import fatto da un altro file.
Windows e macOS si comportano allo stesso modo?
sys.argv è la stessa lista, ma virgolette e caratteri jolly dipendono dalla shell. Ho eseguito i comandi solo su Debian; per Windows ho verificato soltanto, sulla documentazione Microsoft, che il codice di uscita si legge con %ERRORLEVEL% (Prompt dei comandi) o $LASTEXITCODE (PowerShell).
Un punto solo per leggere gli argomenti
La regola è breve: leggi sys.argv[1:] in un punto solo, dentro main(argv=None), controlla il numero degli argomenti, converti i numeri e restituisci un codice di uscita. Per ripassare il pattern main() più guardia torna a Capire if __name__ == "__main__" con esempi; se lo script sta dentro un package e lo lanci con -m, cambiano il nome del modulo e il modo in cui funzionano gli import, come spiega __name__, nome file e package: cosa cambia con le cartelle. Se hai uno script a riga di comando da sistemare e vuoi un punto di partenza che funzioni, è il tipo di lavoro che realizzo su richiesta.