web-dev-qa-db-ja.com

Webスクレイピング用のbashループのexecコマンド

これは、カーリング https://unix.stackexchange.com/ で、結果を配列に格納する単純なスクリプトです。これは正常に機能しています。

#!/usr/local/bin/bash
[ -f pgtoscrap ] && { rm pgtoscrap; };
curl -o pgtoscrap https://unix.stackexchange.com/;
declare -a arr;
fileName="pgtoscrap";

exec 10<&0
exec < $fileName
let count=0
while read LINE; do
    arr[$count]=$LINE
    ((count++))
done
exec 0<10 10<&-

しかし、このスクリプトを実行するたびに。間違ったファイル記述子でエラーが発生します。

./shcrap
./shcrap: line 14: 10: No such file or directory

ループ内でexecコマンドを正しく使用する方法がよくわからないと思います。誰かが説明できますか?

-Bash4にmapfileを実装した後の更新は、はるかに簡単になりました-

#!/usr/local/bin/bash
## Pass a parameter as e.g. ./linkscrapping.bash https://unix.stackexchange.com/
mapfile -t arr < <(curl -s $1); ## Doing exec stuff with process substitution
regex="<a[[:print:]]*<\/a>"; ELEMENTS=${#arr[@]}; firstline=0;
for((i=0;i<$ELEMENTS;i++)); do
    if [[ ${arr[${i}]} =~ $regex ]]; then
    [[ $firstline<1 ]] &&
        { echo ${BASH_REMATCH[0]} > scrapped; let firstline=$firstline+1; } ||
        { echo ${BASH_REMATCH[0]} >> scrapped; }
    fi
done
pg2scrap="scrapped"; mapfile -t arr2 < <(cat $pg2scrap);
regex="href=[\"\'][0-9a-zA-Z\:\/\.]+"; ELEMENTS2=${#arr2[@]}; line2=0
for ((i=0;i<$ELEMENTS2;i++)); do
    if [[ ${arr2[${i}]} =~ $regex ]]; then
    [[ $line2<1 ]] &&
        { echo ${BASH_REMATCH[0]#href=\"} > links; (( line2++ )); } ||
        { echo ${BASH_REMATCH[0]#href=\"} >> links; }
    fi
done; cat links;
1
Rakib Fiha

それは確かに、stdin用に以前に開いたファイル記述子を閉じる方法と関係があります。以下を使用しても問題ありません

_exec 10<&- 
_

_0<10_を実行するときは、現在のディレクトリにある_10_という名前のファイルの内容を検索してSlurpするようにシェルに指示します。これは、このコンテキストでは意味がありますno

bashでは、記述子を閉じるのと同じ目的を達成する別の形式_exec 10>&-_を使用することもできます。

ただし、ランダムファイル記述子でexecを使用して入力を読み取る必要はありません。< <() なので

_while IFS= read -r line; do
    arr["$count"]="$line"
    ((count++))
done< <(pgtoscrap)
_
6
Inian

exec 10<&0は、ファイル記述子番号0から番号10のクローンを作成し、元のファイルを効果的に保存して、次の行のfd0のファイルを置き換えることができるようにします。これを元に戻すには、番号を逆にして、番号10を番号0に複製する必要があります。exec 0<&10(次に、fd10をexec 10<&-で閉じます)。

一方、アンパサンドのないexec 0<10は、ファイル名10を使用した単なるリダイレクトです。そのようなファイルがないため、エラーが発生します。


とはいえ、whileループのリダイレクトを一時的に設定するためにexecを使用する必要はありません。複合コマンドは、次のようにリダイレクトすることもできます。

while read LINE; do
    ...
done < "$filename"

空白やバックスラッシュがデータに影響を与えずに、行全体をそのまま読み取りたい場合は、IFSreadの設定を解除し、read -rを使用する必要があります。また、配列に追加する場合は、手動でインデックスに対応する必要はありません。+=を使用して配列に直接追加できます。

arr=()   # declares it an array and clears it, not strictly necessary though
while IFS= read -r line; do
    arr+=("$line")
done < "$filename"

または、コメントで@BlackJackが言及しているような手動ループの代わりに、mapfilereadarray)を使用します。

mapfile -t arr < "$filename"

または、一時ファイルがまったくない場合でも:

#/bin/bash
mapfile -t arr < <(curl -s https://unix.stackexchange.com/)

-tがない場合、mapfileはラインターミネータをそのまま残します。)

4
ilkkachu