Come posso sostituire una stringa in un file?


751

La sostituzione di stringhe nei file in base a determinati criteri di ricerca è un'attività molto comune. Come posso

  • sostituire la stringa foocon barin tutti i file nella directory corrente?
  • fare lo stesso in modo ricorsivo per le sottodirectory?
  • sostituire solo se il nome del file corrisponde a un'altra stringa?
  • sostituire solo se la stringa si trova in un determinato contesto?
  • sostituire se la stringa si trova su un determinato numero di riga?
  • sostituire più stringhe con la stessa sostituzione
  • sostituire più stringhe con sostituzioni diverse

2
Questo è destinato a essere una domanda e risposta canonica su questo argomento (vedi questa meta discussione ), non esitare a modificare la mia risposta di seguito o aggiungere la tua.
terdon

Risposte:


1009

1. Sostituzione di tutte le occorrenze di una stringa con un'altra in tutti i file nella directory corrente:

Questi sono per i casi in cui si sa che la directory contiene solo file regolari e che si desidera elaborare tutti i file non nascosti. In caso contrario, utilizzare gli approcci in 2.

Tutte le sedsoluzioni in questa risposta assumono GNU sed. Se usi FreeBSD o OS / X, sostituiscilo -icon -i ''. Si noti inoltre che l'uso dello -iswitch con qualsiasi versione sedha determinate implicazioni per la sicurezza del filesystem ed è sconsigliabile in qualsiasi script che si prevede di distribuire in alcun modo.

  • File non ricorsivi, solo in questa directory:

    sed -i -- 's/foo/bar/g' *
    perl -i -pe 's/foo/bar/g' ./* 
    

    ( perlquello fallirà per i nomi di file che terminano con |o spazio) ).

  • File ricorrenti e regolari ( compresi quelli nascosti ) in questa e in tutte le sottodirectory

    find . -type f -exec sed -i 's/foo/bar/g' {} +

    Se stai usando zsh:

    sed -i -- 's/foo/bar/g' **/*(D.)

    (potrebbe non riuscire se l'elenco è troppo grande, vedi zargsper aggirare).

    Bash non può controllare direttamente i file regolari, è necessario un ciclo (le parentesi graffe evitano di impostare le opzioni a livello globale):

    ( shopt -s globstar dotglob;
        for file in **; do
            if [[ -f $file ]] && [[ -w $file ]]; then
                sed -i -- 's/foo/bar/g' "$file"
            fi
        done
    )
    

    I file vengono selezionati quando sono file effettivi (-f) e sono scrivibili (-w).

2. Sostituisci solo se il nome del file corrisponde a un'altra stringa / ha un'estensione specifica / è di un certo tipo ecc:

  • File non ricorsivi, solo in questa directory:

    sed -i -- 's/foo/bar/g' *baz*    ## all files whose name contains baz
    sed -i -- 's/foo/bar/g' *.baz    ## files ending in .baz
    
  • File ricorrenti e regolari in questa e in tutte le sottodirectory

    find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +

    Se stai usando bash (le parentesi graffe evitano di impostare le opzioni a livello globale):

    ( shopt -s globstar dotglob
        sed -i -- 's/foo/bar/g' **baz*
        sed -i -- 's/foo/bar/g' **.baz
    )
    

    Se stai usando zsh:

    sed -i -- 's/foo/bar/g' **/*baz*(D.)
    sed -i -- 's/foo/bar/g' **/*.baz(D.)
    

    Gli --serve per dire sedche non più bandiere saranno fornite nella riga di comando. Questo è utile per proteggere dai nomi dei file che iniziano con -.

  • Se un file è di un certo tipo, ad esempio eseguibile (vedere man findper ulteriori opzioni):

    find . -type f -executable -exec sed -i 's/foo/bar/g' {} +

    zsh:

    sed -i -- 's/foo/bar/g' **/*(D*)

3. Sostituisci solo se la stringa viene trovata in un determinato contesto

  • Sostituisci foocon barsolo se c'è un bazsuccessivo sulla stessa riga:

    sed -i 's/foo\(.*baz\)/bar\1/' file

    In sed, usando \( \)salva qualunque cosa sia tra parentesi e puoi accedervi con \1. Esistono molte varianti di questo tema, per saperne di più su tali espressioni regolari, vedi qui .

  • Sostituisci foocon barsolo se foosi trova sulla colonna 3d (campo) del file di input (presupponendo che i campi siano separati da spazi):

    gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file

    (necessita della versione gawk4.1.0 o successiva).

  • Per un campo diverso basta usare $Ndov'è Nil numero del campo di interesse. Per un diverso separatore di campo ( :in questo esempio) utilizzare:

    gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file

    Un'altra soluzione che utilizza perl:

    perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 

    NOTA: entrambe le soluzioni awke perlinfluenzeranno la spaziatura nel file (rimuovete gli spazi vuoti iniziali e finali e convertite le sequenze di spazi vuoti in un carattere di spazio in quelle righe corrispondenti). Per un campo diverso, utilizzare $F[N-1]dove si Ntrova il numero di campo desiderato e utilizzare un separatore di campo diverso ( $"=":"imposta il separatore di campo di output su :):

    perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
  • Sostituisci foocon barsolo sulla 4a riga:

    sed -i '4s/foo/bar/g' file
    gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
    perl -i -pe 's/foo/bar/g if $.==4' file
    

4. Operazioni di sostituzione multipla: sostituire con stringhe diverse

  • È possibile combinare i sedcomandi:

    sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file

    Essere consapevoli del fatto che l'ordine conta ( sed 's/foo/bar/g; s/bar/baz/g'sostituirà foocon baz).

  • o comandi Perl

    perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
  • Se si dispone di un numero elevato di motivi, è più semplice salvare i motivi e le relative sostituzioni in un sedfile di script:

    #! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    
  • Oppure, se hai troppe coppie di schemi per rendere possibile quanto sopra, puoi leggere coppie di schemi da un file (due motivi separati da spazio, $ modello e $ sostituzione, per riga):

    while read -r pattern replacement; do   
        sed -i "s/$pattern/$replacement/" file
    done < patterns.txt
    
  • Sarà piuttosto lento per lunghi elenchi di schemi e file di dati di grandi dimensioni, quindi potresti voler leggere gli schemi e creare uno sedscript da essi. Quanto segue presuppone che un delimitatore <spazio> separa un elenco di coppie MATCH <spazio> REPLACE che si verificano una per riga nel file patterns.txt:

    sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt |
    sed -f- ./editfile >outfile
    

    Il formato sopra è in gran parte arbitrario e, ad esempio, non consente uno <spazio> in MATCH o REPLACE . Il metodo è molto generico: in sostanza, se è possibile creare un flusso di output che assomiglia a uno sedscript, è possibile generare tale flusso come sedscript specificando sedil file di script come -stdin.

  • È possibile combinare e concatenare più script in modo simile:

    SOME_PIPELINE |
    sed -e'#some expression script'  \
        -f./script_file -f-          \
        -e'#more inline expressions' \
    ./actual_edit_file >./outfile
    

    Un POSIX sedconcatenerà tutti gli script in uno nell'ordine in cui compaiono sulla riga di comando. Nessuno di questi deve finire in una \newline.

  • grep può funzionare allo stesso modo:

    sed -e'#generate a pattern list' <in |
    grep -f- ./grepped_file
    
  • Quando si lavora con stringhe fisse come schemi, è buona norma sfuggire ai metacaratteri delle espressioni regolari . Puoi farlo piuttosto facilmente:

    sed 's/[]$&^*\./[]/\\&/g
         s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
    ' <patterns.txt |
    sed -f- ./editfile >outfile
    

5. Operazioni di sostituzione multipla: sostituisci più pattern con la stessa stringa

  • Sostituire qualsiasi foo, baro bazconfoobar

    sed -Ei 's/foo|bar|baz/foobar/g' file
  • o

    perl -i -pe 's/foo|bar|baz/foobar/g' file

2
@ StéphaneChazelas grazie per la modifica, ha davvero risolto diverse cose. Tuttavia, ti preghiamo di non rimuovere le informazioni rilevanti per bash. Non tutti usano zsh. Certamente aggiungi zshinformazioni ma non c'è motivo di rimuovere la roba bash. Inoltre, so che usare la shell per l'elaborazione del testo non è l'ideale ma ci sono casi in cui è necessario. Ho modificato una versione migliore del mio script originale che creerà uno sedscript invece di utilizzare effettivamente il loop di shell per analizzare. Questo può essere utile se hai ad esempio diverse centinaia di paia di motivi.
terdon

2
@terdon, il tuo bash non è corretto. bash prima della 4.3 seguiranno i symlink durante la discesa. Inoltre bash non ha equivalenti per il (.)qualificatore globbing, quindi non può essere usato qui. (ne manchi anche un po '). Il ciclo for è errato (manca -r) e significa fare diversi passaggi nei file e non aggiunge alcun vantaggio rispetto a uno script sed.
Stéphane Chazelas,

7
@terdon Cosa indica --dopo sed -ie prima del comando sostitutivo?
Geek,

5
@Geek è una cosa POSIX. Indica la fine delle opzioni e consente di passare argomenti a partire da -. Usandolo si assicura che i comandi funzioneranno su file con nomi come -foo. Senza di esso, -fsarebbe analizzato come opzione.
Terdon

1
Fai molta attenzione ad eseguire alcuni dei comandi ricorsivi nei repository git. Ad esempio, le soluzioni fornite nella sezione 1 di questa risposta modificheranno effettivamente i file git interni in una .gitdirectory e incasineranno il tuo checkout. Meglio operare all'interno / su directory specifiche per nome.
Pistos

75

Un buon R e pl acement strumento di Linux è RPL , che è stato scritto in origine per il progetto Debian, così è disponibile con apt-get install rplin qualsiasi distro derivata di Debian, e può essere per gli altri, ma per il resto è possibile scaricare il tar.gzfile in SourgeForge .

Il più semplice esempio di utilizzo:

 $ rpl old_string new_string test.txt

Nota che se la stringa contiene spazi dovrebbe essere racchiusa tra virgolette. Per impostazione predefinita, rplprenditi cura delle lettere maiuscole ma non delle parole complete , ma puoi modificare queste impostazioni predefinite con le opzioni -i(ignora maiuscole e minuscole) e -w(parole intere). Puoi anche specificare più file :

 $ rpl -i -w "old string" "new string" test.txt test2.txt

O anche specificare le estensioni ( -x) da cercare o anche ricorsivamente ( -R) nella directory:

 $ rpl -x .html -x .txt -R old_string new_string test*

Puoi anche cercare / sostituire in modalità interattiva con l' -popzione (prompt):

L'output mostra i numeri di file / stringa sostituiti e il tipo di ricerca (maiuscole / minuscole, parole intere / parziali), ma può essere silenzioso con l' opzione -q( modalità silenziosa ) o ancora più dettagliata, elencando i numeri di riga che contengono corrispondenze di ciascun file e directory con l' opzione -v( modalità dettagliata ).

Altre opzioni che vale la pena ricordare sono -e(honor e scapes) che consentono regular expressions, quindi è possibile cercare anche schede ( \t), nuove righe ( \n), ecc. Anche tu puoi usare -fper forzare i permessi (ovviamente, solo quando l'utente ha i permessi di scrittura) e -dper preservare i tempi di modifica`).

Infine, se non sei sicuro di ciò che farà esattamente, usa la -s( modalità simulazione ).


2
Molto meglio al feedback e alla semplicità rispetto a sed. Vorrei solo che permettesse di agire sui nomi dei file, e quindi sarebbe perfetto così com'è.
Kzqai,

1
mi piace il -s (modalità simulazione) :-)
erm3nda

25

Come eseguire una ricerca e sostituire più file suggerisce:

Puoi anche usare find e sed, ma trovo che questa piccola riga di perl funzioni bene.

perl -pi -w -e 's/search/replace/g;' *.php
  • -e significa eseguire la seguente riga di codice.
  • -i significa modifica sul posto
  • -w scrivere avvertimenti
  • -p passa sopra il file di input, stampando ogni riga dopo che lo script è stato applicato ad esso.

I miei migliori risultati provengono dall'uso di perl e grep (per garantire che il file abbia l'espressione di ricerca)

perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )

13

Puoi usare Vim in modalità Ex:

sostituire la stringa ALF con BRA in tutti i file nella directory corrente?

for CHA in *
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

fare lo stesso in modo ricorsivo per le sottodirectory?

find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'

sostituire solo se il nome del file corrisponde a un'altra stringa?

for CHA in *.txt
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

sostituire solo se la stringa si trova in un determinato contesto?

ex -sc 'g/DEL/s/ALF/BRA/g' -cx file

sostituire se la stringa si trova su un determinato numero di riga?

ex -sc '2s/ALF/BRA/g' -cx file

sostituire più stringhe con la stessa sostituzione

ex -sc '%s/\vALF|ECH/BRA/g' -cx file

sostituire più stringhe con sostituzioni diverse

ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file

13

Ho usato questo:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. Elenca tutti i file che contengono old_string.

  2. Sostituisci newline nel risultato con spazi (in modo che l'elenco dei file possa essere alimentato sed.

  3. Esegui sedsu quei file per sostituire la vecchia stringa con la nuova.

Aggiornamento: il risultato sopra riportato fallirà sui nomi di file che contengono spazi bianchi. Invece, usa:

grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'


Nota che ciò non andrà a buon fine se uno dei nomi dei tuoi file contiene spazi, schede o nuove righe. L'uso grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'consentirà di gestire nomi di file arbitrari.
terdon

grazie ragazzi. aggiunto aggiornamento e lasciato il vecchio codice perché è un avvertimento interessante che potrebbe essere utile a qualcuno ignaro di questo comportamento.
o_o_o--

6

Dal punto di vista dell'utente, uno strumento Unix bello e semplice che fa perfettamente il lavoro è qsubst. Per esempio,

% qsubst foo bar *.c *.h

sostituirà foocon barin tutti i miei file C. Una caratteristica interessante è che qsubsteseguirà una sostituzione della query , cioè mi mostrerà ogni ricorrenza fooe mi chiederà se voglio sostituirla o meno. [Puoi sostituire incondizionatamente (senza chiedere) con l' -goopzione, e ci sono altre opzioni, ad esempio, -wse vuoi sostituire solo fooquando è una parola intera.]

Come ottenerlo: è qsubststato inventato da Der Mouse (di McGill) e pubblicato su comp.unix.sources 11 (7) nell'agosto 1987. Esistono versioni aggiornate. Ad esempio, la versione NetBSD si qsubst.c,v 1.8 2004/11/01compila e funziona perfettamente sul mio mac.


2

Avevo bisogno di qualcosa che avrebbe fornito un'opzione di lavaggio a correre e dovrebbe funzionare in modo ricorsivo con un glob, e dopo aver tentato di farlo con awke sedho rinunciato e invece fatto in pitone.

Lo script cerca in modo ricorsivo tutti i file corrispondenti a un modello glob (ad esempio --glob="*.html") per una regex e sostituisce con la regex sostitutiva:

find_replace.py [--dir=my_folder] \
    --search-regex=<search_regex> \
    --replace-regex=<replace_regex> \
    --glob=[glob_pattern] \
    --dry-run

Ogni opzione lunga come --search-regexha un'opzione corta corrispondente, ad es -s. Corri con -hper vedere tutte le opzioni.

Ad esempio, questo capovolge tutte le date da 2017-12-31a 31-12-2017:

python replace.py --glob=myfile.txt \
    --search-regex="(\d{4})-(\d{2})-(\d{2})" \
    --replace-regex="\3-\2-\1" \
    --dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re

import argparse

def find_replace(cfg):
    search_pattern = re.compile(cfg.search_regex)

    if cfg.dry_run:
        print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')

    for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
        for filename in fnmatch.filter(files, cfg.glob):

            if cfg.print_parent_folder:
                pardir = os.path.normpath(os.path.join(path, '..'))
                pardir = os.path.split(pardir)[-1]
                print('[%s]' % pardir)
            filepath = os.path.join(path, filename)

            # backup original file
            if cfg.create_backup:
                backup_path = filepath + '.bak'

                while os.path.exists(backup_path):
                    backup_path += '.bak'
                print('DBG: creating backup', backup_path)
                shutil.copyfile(filepath, backup_path)

            with open(filepath) as f:
                old_text = f.read()

            all_matches = search_pattern.findall(old_text)

            if all_matches:

                print('Found {} matches in file {}'.format(len(all_matches), filename))

                new_text = search_pattern.sub(cfg.replace_regex, old_text)

                if not cfg.dry_run:
                    with open(filepath, "w") as f:
                        print('DBG: replacing in file', filepath)
                        f.write(new_text)
                else:
                    for idx, matches in enumerate(all_matches):
                        print("Match #{}: {}".format(idx, matches))

                    print("NEW TEXT:\n{}".format(new_text))

            elif cfg.verbose:
                print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))


if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='''DESCRIPTION:
    Find and replace recursively from the given folder using regular expressions''',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog='''USAGE:
    {0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]

    '''.format(os.path.basename(sys.argv[0])))

    parser.add_argument('--dir', '-d',
                        help='folder to search in; by default current folder',
                        default='.')

    parser.add_argument('--search-regex', '-s',
                        help='search regex',
                        required=True)

    parser.add_argument('--replace-regex', '-r',
                        help='replacement regex',
                        required=True)

    parser.add_argument('--glob', '-g',
                        help='glob pattern, i.e. *.html',
                        default="*.*")

    parser.add_argument('--dry-run', '-dr',
                        action='store_true',
                        help="don't replace anything just show what is going to be done",
                        default=False)

    parser.add_argument('--create-backup', '-b',
                        action='store_true',
                        help='Create backup files',
                        default=False)

    parser.add_argument('--verbose', '-v',
                        action='store_true',
                        help="Show files which don't match the search regex",
                        default=False)

    parser.add_argument('--print-parent-folder', '-p',
                        action='store_true',
                        help="Show the parent info for debug",
                        default=False)

    config = parser.parse_args(sys.argv[1:])

    find_replace(config)

Here è una versione aggiornata dello script che evidenzia i termini di ricerca e le sostituzioni con colori diversi.


1
Non capisco perché dovresti creare qualcosa di così complesso. Per la ricorsione, usa l' globstaropzione bash (o l'equivalente della tua shell) e **globs o find. Per una corsa a secco, basta usare sed. Se non si utilizza l' -iopzione, non verrà apportata alcuna modifica. Per un backup utilizzare sed -i.bak(o perl -i .bak); per i file che non corrispondono, utilizzare grep PATTERN file || echo file. E perché nel mondo dovresti fare in modo che Python espanda il glob invece di lasciarlo fare alla shell? Perché script.py --glob=foo*invece di solo script.py foo*?
terdon

1
I miei perché sono molto semplici: (1) soprattutto, facilità di debug; (2) utilizzando solo un unico strumento ben documentato con una comunità di supporto (3) che non lo sappia sede awkche non sia disposto a investire tempo extra per padroneggiarli, (4) leggibilità, (5) questa soluzione funzionerà anche su sistemi non posix (non che ne abbia bisogno, ma qualcun altro potrebbe).
ccpizza,

1

ripgrep (nome comando rg) è uno grepstrumento, ma supporta anche la ricerca e la sostituzione.

$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky

$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky


rg non supporta l'opzione sul posto, quindi dovrai farlo da solo

$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky


Consultare la documentazione di regex di Rust per la sintassi e le funzioni delle espressioni regolari. L' -Pinterruttore abiliterà il sapore PCRE2 . rgsupporta Unicode per impostazione predefinita.

$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat

$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)

$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.

$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[$0]'
[car] bat [cod] map


Ad esempio grep, l' -Fopzione consentirà di far corrispondere le stringhe fisse, un'opzione utile che ritengo seddebba implementare anche.

$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29


Un'altra opzione utile è -Uche consente la corrispondenza multilinea

$ # (?s) flag will allow . to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi  Day


rg può gestire anche file in stile dos

$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123


Un altro vantaggio rgè che è probabile che sia più veloce dised

$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real    0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real    0m0.007s

$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt    
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real    0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real    0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical

$ time LC_ALL=C sed -E 's/\b(\w+)(\s+\1)+\b/\1/g' big.txt > f1
real    0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real    0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.