2012-04-27 2 views
31

Voglio:Come aprire (leggere-scrivere) o creare un file con troncamento consentito?

  • aprire un file in modalità lettura-scrittura se esiste;
  • crealo se non esiste;
  • essere in grado di troncarlo sempre e ovunque.

EDIT: con troncare intendo scrivere fino posizione e scartare la parte rimanente del file, se presente

Tutto questo atomicamente (con un singolo open() chiamata o simulare un singolo open() chiamata)

Nessuna singola modalità aperta sembra applicarsi:

  • r: ovviamente non funziona;
  • r +: fallisce se il file non esiste;
  • w: ricrea il file se esiste;
  • w +: ricrea il file se esiste;
  • a: non può leggere;
  • a +: non può troncare.

Alcune combinazioni che ho provato (rw, rw +, r + w, ecc.) Sembra non funzionare neanche. È possibile?

Alcuni doc da Ruby (vale anche per python troppo):

r 
Read-only mode. The file pointer is placed at the beginning of the file. 
This is the default mode. 

r+ 
Read-write mode. The file pointer will be at the beginning of the file. 

w 
Write-only mode. Overwrites the file if the file exists. If the file 
does not exist, creates a new file for writing. 

w+ 
Read-write mode. Overwrites the existing file if the file exists. If the 
file does not exist, creates a new file for reading and writing. 

a 
Write-only mode. The file pointer is at the end of the file if the file 
exists. That is, the file is in the append mode. If the file does not exist, 
it creates a new file for writing. 

a+ 
Read and write mode. The file pointer is at the end of the file if the file 
exists. The file opens in the append mode. If the file does not exist, it 
creates a new file for reading and writing. 
+0

Quindi in sostanza desidera sovrascrivere un file e assicurarsi che i contenuti precedenti non rimangano al di sotto del punto in cui hai smesso di scrivere? –

+0

Sì. 'file.truncate()' funziona per questo scopo e funziona quando il file è "r +", "w", "w +". Ma hanno tutti i difetti che ho elencato sopra. – ceztko

risposta

28

Secondo OpenGroup:

O_TRUNC

Se il file esiste ed è un file regolare, e il file è successo aperto O_RDWR o O_WRONLY, la sua lunghezza è troncato a 0 e la modalità e il proprietario è invariato. Non avrà alcun effetto sui file speciali FIFO o sui file del dispositivo terminale. Il suo effetto su altri tipi di file è dipendente dall'implementazione. Il risultato dell'utilizzo di O_TRUNC con O_RDONLY è indefinito.

Quindi, O_TRUNC è probabilmente passato quando si apre un file con "w" o "w +". Questo dà "troncamento" un significato diverso, non quello che voglio.

Con python la soluzione sembra aprire il file a I/O di basso livello con la funzione os.open().

La seguente funzione di pitone:

def touchopen(filename, *args, **kwargs): 
    # Open the file in R/W and create if it doesn't exist. *Don't* pass O_TRUNC 
    fd = os.open(filename, os.O_RDWR | os.O_CREAT) 

    # Encapsulate the low-level file descriptor in a python file object 
    return os.fdopen(fd, *args, **kwargs) 

ha il comportamento che volevo. Si può usare in questo modo (è infatti il ​​mio caso d'uso):

# Open an existing file or create if it doesn't exist 
with touchopen("./tool.run", "r+") as doing_fd: 

    # Acquire a non-blocking exclusive lock 
    fcntl.lockf(doing_fd, fcntl.LOCK_EX) 

    # Read a previous value if present 
    previous_value = doing_fd.read() 
    print previous_value 

    # Write the new value and truncate 
    doing_fd.seek(0) 
    doing_fd.write("new value") 
    doing_fd.truncate() 
+0

apre con os.O_RDWR lo apre in modalità di sola lettura con me su python 2.6 in linux – rutherford

+1

Sono davvero agitato dall'uso della parola "truncate" applicata ai file. Truncate ha sempre significato (per me) qualcosa come "clip breve". È decisamente diverso nel significato (ancora una volta, per me) da "sovrascrivere". –

+0

@StevenLu, sono perfettamente d'accordo con te. Ad ogni modo, l'importante è che il comportamento desiderato sia ottenibile senza troppi sforzi. Questo codice era necessario, come Ivo, per scrivere un blocco del server con un contesto. Non ricordo ora, ma ho trovato non desiderabile per memorizzare il contesto/informazioni in un file diverso. Forse volevo solo imitare il comportamento dei file /var/run/foo.pid, che sono spesso usati come lock e memorizzare il pid del demone correntemente in esecuzione. – ceztko

0

Non so di alcun modo elegante per fare esattamente questo in Ruby. La mia soluzione sarebbe probabilmente quella di creare un file temporaneo, scrivere contenuti ad esso, e quindi rinominarlo al nome del file che volevo davvero. Questo sovrascriverebbe il file precedente, se esistente, o creare il file se non fosse così. Qualcosa di simile a questo:

orig_filename = './whatever_file.log' 
temp_filename = './.tempfile' 
temp_file = File.new(temp_filename, 'w') 

// Write contents to file 

temp_file.close 
File.rename(temp_filename, orig_filename) 

La ridenominazione alzerà SystemCallError se non riesce per qualsiasi motivo.

13

Bene, ci sono solo queste modalità, e tutte hanno i "difetti" che hai elencato.

L'unica opzione è quella di avvolgere open(). Perché non qualcosa del genere? (Python)

def touchopen(filename, *args, **kwargs): 
    open(filename, "a").close() # "touch" file 
    return open(filename, *args, **kwargs) 

si comporta proprio come aperta, si potrebbe anche associare nuovamente per aprire() se si desidera veramente.

tutte le funzioni di aperti sono conservati, si può anche fare:

with touchopen("testfile", "r+") as testfile: 
    do_stuff() 

Si potrebbe comunque creare un contextmanager che si apre il file in una modalità +, si legge nella memoria, e intercetta scrive in modo da gestire il troncamento creando magicamente un file temporaneo in modalità w e rinomina quel file temporaneo nel tuo file originale quando lo chiudi, ma sarebbe eccessivo.

+1

+1 perché hai aggiunto alcuni indizi importanti. Penso di aver trovato una risposta migliore (eventualmente) soggetta a meno condizioni di gara. Sta arrivando a breve. – ceztko

+0

Aggiunta la mia risposta. – ceztko

+0

ah, capisco. Ho cercato qualcosa di simile, ma non l'ho trovato. bella scoperta, se funziona davvero, davvero bello! – ch3ka

2

È possibile leggere, scrivere e troncare con "A +" (Ruby):

File.open("test.txt", "a+") do |f| 
    f.print "abc\ndefgh" 
    f.rewind 
    p f.read 
    f.truncate(5) 
end 
puts File.size("test.txt") #=> 5 
+0

Sto usando Python, ma il comportamento sembra identico rispetto al rubino. Anche se è vero che 'truncate (size)' funziona, 'truncate()' senza argomenti non funziona allo stesso modo in modalità append (almeno in linux). La mia soluzione raggiunge il comportamento esatto che volevo. – ceztko

+0

In ruby ​​'truncate' deve avere un argomento. 'f.truncate (f.pos)' farebbe qualcosa come "tronca qui!". – steenslag

+0

Bene, vero :) Un altro piccolo errore è che in modalità append la posizione iniziale è la fine del file, non l'inizio. – ceztko