Python pratico

`sys.argv` dentro `main()`: argomenti da terminale senza argparse

15 min di lettura
Fila di cassette postali metalliche d’epoca, verdi e decorate, ciascuna con una serratura al centro e una targhetta nera con un numero bianco
Windows macOS Linux
Foto: Jackie A · Pexels · Licenza Pexels

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 chiamo lab-argv. Su Windows usa python al posto di python3; 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.

Terminale Linux nella cartella lab-argv con tre comandi: python3 saluta_argv.py Alex stampa «Ciao, Alex!», python3 saluta_argv.py senza argomenti termina con IndexError: list index out of range, python3 mostra_argv.py uno due elenca argv completo, argv[0], gli argomenti veri e il loro numero
Terminale Linux: lo stesso script con e senza argomento. Senza argomento sys.argv ha un solo elemento e l’indice 1 dà IndexError; con mostra_argv.py si vede la lista completa. Screenshot: ai riparo

Il 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.

Pacco di carta kraft legato con spago di juta, con un’etichetta di carta a scrittura corsiva e un piccolo acquerello, accanto ad altri pacchi e a un candeliere di ottone
Un pacco ha una sola etichetta, anche se porta più parole: nel terminale sono le virgolette a tenere insieme un valore con gli spazi. Foto: betül nur akyürek · Pexels

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.

Terminale Linux nella cartella lab-argv con tre comandi: python3 usa_ripeti.py stampa due saluti e «main() ha restituito 0», python3 usa_letto_in_cima.py Mario mostra che il modulo importato ha letto 'Mario', python3 usa_letto_in_cima.py senza argomenti termina con IndexError durante l'import
Terminale Linux: 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 import non deve leggere gli argomenti: se sys.argv viene letto fuori da main(), un semplice import può fallire o comportarsi in modo diverso a seconda di chi lancia il programma. Il pattern è: leggi sys.argv[1:] in un punto solo, passa i valori a main() 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 range su sys.argv[1] → hai lanciato lo script senza argomenti e il codice legge l’indice senza controllare → verifica len(argv) all’inizio di main(), stampa l’uso su stderr e restituisci 2.
  • ValueError: invalid literal for int() with base 10: 'tre' → hai passato testo a int() senza intercettare l’errore (nella prova è il messaggio che int('tre') produce da solo) → racchiudi la conversione in try/except ValueError e 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 in VOLTE) → metti il valore tra virgolette doppie.
  • IndexError appena importi il file → qualcuno legge sys.argv fuori da main() e l’import lo esegue → sposta la lettura dentro main() e lascia a sys.exit(main()) il compito di chiamarla.
  • Un messaggio d’uso con -c o un percorso lungo → hai usato argv[0], che cambia con il modo in cui avvii il file → scrivi il nome del file a mano nella stringa USO.

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.

Alex Lunalbi

Alex Lunalbi

Security & privacy

Dona su PayPal

Scrivo di sicurezza digitale pratica e codice IA. Su ai riparo ogni procedura è verificata su macchine reali.

Tag
  • python,
  • terminale,
  • cli,
  • script