Lettura carattere per carattere con lettura bash


8

Ho provato a usare bash per leggere un file carattere per carattere.

Dopo molte prove ed errori, ho scoperto che funziona:

exec 4<file.txt 
declare -i n
while read -r ch <&4; 
     n=0
     while [ ! $n -eq ${#ch} ]
           do  echo -n "${ch:$n:1}"
               (( n++ ))
          done
     echo "" 
     done

Vale a dire, posso leggerlo riga per riga e quindi scorrere ogni carattere di riga per carattere.

Prima di farlo, ci avevo provato: exec 4<file.txt && while read -r -n1 ch <&4; do; echo -n "$ch"; done ma saltava tutti gli spazi bianchi nel file .

Potresti spiegare perché? C'è un modo per far funzionare la seconda strategia (cioè leggere i caratteri con i caratteri con la lettura di Bash)?


4
Non impostare IFSnulla per far sì che gli spazi bianchi sopravvivano alla divisione delle parole.
arte

Ho provato che con IFS = '', ma suppongo che dovesse essere solo IFS =. Grazie!
PSkocik,

Risposte:


12

È necessario rimuovere i caratteri degli spazi bianchi dal $IFSparametro per readinterrompere il salto di quelli iniziali e finali (con -n1, il carattere degli spazi bianchi, se presente, sarebbe sia iniziale che finale, quindi ignorato):

while IFS= read -rn1 a; do printf %s "$a"; done

Ma anche allora bash readsalterà i personaggi newline, con i quali puoi aggirare:

while IFS= read -rn1 a; do printf %s "${a:-$'\n'}"; done

Sebbene tu possa usare IFS= read -d '' -rn1invece o anche meglio IFS= read -N1(aggiunto in 4.1, copiato da ksh93(aggiunto in o)) quale è il comando per leggere un carattere.

Nota che bash readnon può far fronte ai personaggi NUL. E ksh93 ha gli stessi problemi di bash.

Con zsh:

while read -ku0 a; do print -rn -- "$a"; done

(zsh può far fronte ai caratteri NUL).

Si noti che quelli read -k/n/Nleggono un numero di caratteri , non byte . Pertanto, per i caratteri multibyte, potrebbe essere necessario leggere più byte fino a quando non viene letto un carattere completo. Se l'input contiene caratteri non validi, si può finire con una variabile che contiene una sequenza di byte che non forma caratteri validi e che la shell potrebbe finire per contare come più caratteri . Ad esempio in una locale UTF-8:

$ printf '\375\200\200\200\200ABC' | bash -c '
    IFS= read  -rN1 a; echo "${#a}"'
6

Ciò \375introdurrebbe un carattere UTF-8 a 6 byte. Tuttavia, il sesto ( A) sopra non è valido per un carattere UTF-8. Si finisce ancora con \375\200\200\200\200Ain $a, che bashconta come 6 caratteri sebbene i primi 5 non siano realmente personaggi, solo 5 byte che non fanno parte di alcun personaggio.


Grazie. Semplice e bella. In realtà ho provato qualcosa a tal fine (modificando la variabile IFS), ma in qualche modo non ha funzionato per me, quindi ho finito con quel mio intruglio (giocare inutilmente con i descrittori di file, ecc.).
PSkocik,

1
È interessante notare che l'utilizzo read -rN1invece risolve il problema della nuova riga e quindi elimina la necessità di fornire una nuova riga come impostazione predefinita durante la stampa $a.
krb686,

Solo FTR sto leggendo 4118 file da 20 MB di riga. L'uso di read -n1(char by char) richiede 4 minuti e 51 secondi e riscalda il laptop a 90 gradi. L'utilizzo read -r(riga per riga) richiede 1,3 secondi e il laptop rimane a 54 gradi con due ventole silenziose.
WinEunuuchs2Unix

2

Questo è un semplice esempio usando cut, un forciclo e wc:

bytes=$(wc -c < /etc/passwd)
file=$(</etc/passwd)

for ((i=0; i<bytes; i++)); do
    echo $file | cut -c $i
done

KISS non è vero?


Se questo è il bacio, allora che cosa è una pura bashsoluzione: file="$(</etc/passwd)"; bytes="${#file}"; for ((i=0;i<bytes;i++)); do echo "${file:i:1}"; done?
arte

Grazie ad entrambi. Sì, se dovessi ricorrere a prendere quei personaggi dalle righe, potrei anche prenderli dall'intero file. Trovo però la soluzione di sch il più BACIO.
PSkocik,

@manatwork Questa è una soluzione buona e semplice. Anche così, mi sembra che la risposta sopra usando un ciclo di lettura sia un po 'più veloce per qualche motivo. Forse le sottostringhe in bash sono abbastanza lente?
krb686,

@ krb686, in realtà l'intero bash"È troppo grande e troppo lento". secondo la sezione BUGS della sua pagina man. Ma anche così, è ancora più veloce tagliare una stringa in memoria che leggere un file ancora e ancora per ogni personaggio. Almeno sulla mia macchina: pastebin.com/zH5trQQs
manatwork
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.