Perché c'è una condizione di gara
I due lati di un tubo vengono eseguiti in parallelo, non uno dopo l'altro. C'è un modo molto semplice per dimostrarlo: corri
time sleep 1 | sleep 1
Questo richiede un secondo, non due.
La shell avvia due processi figlio e attende il completamento di entrambi. Questi due processi vengono eseguiti in parallelo: l'unico motivo per cui uno di essi si sincronizzerebbe con l'altro è quando deve attendere l'altro. Il punto più comune di sincronizzazione è quando il lato destro si blocca in attesa della lettura dei dati sull'input standard e si sblocca quando il lato sinistro scrive più dati. Il contrario può anche accadere, quando il lato destro è lento nella lettura dei dati e il lato sinistro si blocca nella sua operazione di scrittura fino a quando il lato destro non legge più dati (c'è un buffer nel pipe stesso, gestito dal kernel, ma ha una dimensione massima ridotta).
Per osservare un punto di sincronizzazione, osservare i seguenti comandi ( sh -xstampa ogni comando mentre lo esegue):
time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'
Gioca con le variazioni fino a quando non ti senti a tuo agio con ciò che osservi.
Dato il comando composto
cat tmp | head -1 > tmp
il processo a sinistra esegue le seguenti operazioni (ho elencato solo i passaggi rilevanti per la mia spiegazione):
- Eseguire il programma esterno
catcon l'argomento tmp.
- Aperto
tmpper la lettura.
- Sebbene non abbia raggiunto la fine del file, leggi un blocco dal file e scrivilo nell'output standard.
Il processo a destra procede come segue:
- Reindirizzare l'output standard su
tmp, troncando il file nel processo.
- Eseguire il programma esterno
headcon l'argomento -1.
- Leggere una riga dallo standard input e scriverlo nello standard output.
L'unico punto di sincronizzazione è che right-3 attende che left-3 abbia elaborato una riga completa. Non c'è sincronizzazione tra left-2 e right-1, quindi possono avvenire in entrambi gli ordini. L'ordine in cui si verificano non è prevedibile: dipende dall'architettura della CPU, dalla shell, dal kernel, da quali core si pianificano i processi, da ciò che interrompe la CPU in quel momento, ecc.
Come cambiare il comportamento
Non è possibile modificare il comportamento modificando un'impostazione di sistema. Il computer fa quello che gli dici di fare. Gli hai detto di troncare tmpe leggere tmpin parallelo, quindi fa le due cose in parallelo.
Ok, c'è un'impostazione di sistema che potresti cambiare: potresti sostituirla /bin/bashcon un altro programma che non è bash. Spero che sia ovvio che questa non è una buona idea.
Se si desidera che il troncamento avvenga prima del lato sinistro del tubo, è necessario inserirlo all'esterno della tubazione, ad esempio:
{ cat tmp | head -1; } >tmp
o
( exec >tmp; cat tmp | head -1 )
Non ho idea del perché tu voglia questo però. Qual è il punto di lettura di un file che sai essere vuoto?
Al contrario, se si desidera che il reindirizzamento dell'output (incluso il troncamento) si verifichi al cattermine della lettura, è necessario bufferizzare completamente i dati in memoria, ad es.
line=$(cat tmp | head -1)
printf %s "$line" >tmp
oppure scrivi in un altro file e poi spostalo in posizione. Questo è di solito il modo più efficace per eseguire operazioni negli script e presenta il vantaggio che il file viene scritto per intero prima che sia visibile attraverso il nome originale.
cat tmp | head -1 >new && mv new tmp
La collezione moreutils include un programma che fa proprio questo, chiamato sponge.
cat tmp | head -1 | sponge tmp
Come rilevare il problema automaticamente
Se il tuo obiettivo era prendere script scritti male e capire automaticamente dove si rompono, allora scusa, la vita non è così semplice. L'analisi di runtime non trova in modo affidabile il problema perché a volte cattermina la lettura prima che si verifichi il troncamento. L'analisi statica può in linea di principio farlo; l'esempio semplificato nella tua domanda viene colto da Shellcheck , ma potrebbe non rilevare un problema simile in uno script più complesso.