bashで文字を1文字ずつ読み取る


8

私はbashを使用してファイルを1文字ずつ読み取ろうとしています。

多くの試行錯誤の結果、これが機能することがわかりました。

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

つまり、1行ずつ読み取って、各行をcharごとにループできます。

これを行う前に、私は試し ましたが、ファイル内のすべての空白exec 4<file.txt && while read -r -n1 ch <&4; do; echo -n "$ch"; doneスキップされました

理由を教えてください。2番目の戦略(つまり、bashの読み取りで文字ごとに読み取る)を機能させる方法はありますか?


4
IFS空白を単語分割後も存続させるには、何も設定しません。
manatwork 2012年

IFS = ''でそれを試してみましたが、それは単なるIFS =でなければならなかったと思います。ありがとう!
PSkocik

回答:


12

先頭と末尾の空白文字をスキップしないようにするには、$IFSパラメーターから空白文字を削除する必要がありreadます(-n1先頭と末尾の両方に空白文字がある場合は空白文字をスキップするため):

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

しかし、それでもbash readは改行文字をスキップするので、次のように対処できます。

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

あなたは使用することができますがIFS= read -d '' -rn1代わりに、またはより良いIFS= read -N1(からコピーされ、4.1で追加ksh93(に追加o))1つの文字を読むためのコマンドです。

bash readはNUL文字に対応できないことに注意してください。また、ksh93にはbashと同じ問題があります。

zshの場合:

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

(zshはNUL文字に対応できます)。

それらはバイトではなくread -k/n/N多くの文字を読み取ることに注意してください。したがって、マルチバイト文字の場合、完全な文字が読み取られるまで複数バイトを読み取る必要がある場合があります。入力に無効な文字が含まれている場合、有効な文字を形成しない一連のバイトを含む変数になり、シェルが数文字としてカウントする可能性があります。たとえば、UTF-8ロケールの場合:

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

これ\375により、6バイトのUTF-8文字が導入されます。ただし、上記の6番目(A)はUTF-8文字には無効です。あなたはまだ\375\200\200\200\200Ain $aで終わりますが、最初の5文字は実際には文字ではなく、5バイトだけが文字の一部を形成していませんが、bash6 文字としてカウントされます。


ありがとう。シンプルで美しい。私は実際にこの目的のために何かを試みましたが(IFS変数の変更)、それは私にとってはうまくいきませんでした。
PSkocik

1
興味深いことに、read -rN1代わりにを使用すると改行の問題が解決され、印刷時に改行をデフォルトとして指定する必要がなくなります$a
krb686

FTRだけで4118行の20 MBファイルを読み取っています。read -n1(char by char)の使用には4分 51秒かかり、ラップトップを90度に加熱します。read -r(1行ずつ)を使用すると1.3秒かかり、ラップトップは54度に留まり、デュアルファンはサイレントです。
WinEunuuchs2Unix 2018年

2

これは、使用した簡単な例でcutforループ&wc

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

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

KISSじゃない?


それがKISSの場合、純粋なbash解決策は何file="$(</etc/passwd)"; bytes="${#file}"; for ((i=0;i<bytes;i++)); do echo "${file:i:1}"; doneですか?
manatwork 2012年

両方に感謝します。うん、行からこれらの文字を取得する必要がある場合は、ファイル全体から取得することもできます。私はschのソリューションが最もKISSだと思います。
PSkocik

@manatworkこれは優れたシンプルなソリューションです。それでも、何らかの理由で、読み取りループを使用した上記の答えはかなり速いように思えます。多分bashの部分文字列はかなり遅いですか?
krb686

@ krb686、実際には全体がbash「大きすぎて遅すぎる」。マニュアルページのバグセクションによると。ただし、それでも、ファイルを文字ごとに何度も読み取るよりも、メモリ内の文字列をスライスする方が高速です。私のマシン上に、少なくとも:pastebin.com/zH5trQQs
manatwork
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.