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

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ループ内でコマンドを正しく使用する方法をよく理解していないようです。誰かがこれを説明できますか?

- Bash 4を実装した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

これは、以前にstdin用に開いたファイル記述子を閉じる方法に関連しているはずです。以下を使うと大丈夫でしょう

exec 10<&- 

これにより、現在のディレクトリに指定されているファイルの内容を0<10見つけて読み取るようにシェルに指示します。10いいえこの文脈では意味があります。

記述子を閉じるのと同じ目的を達成するために、他のフォームを使用することもbashできます。exec 10>&-

しかし、つまり、任意のファイル記述子を使用して入力を読み取る必要はなく、次の形式のプロセスexec置換技術を使用して入力を読み取ることができます。bash< <()

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

答え2

exec 10<&0ファイルディスクリプタ番号0を番号10に複製して、次の行のfd 0のファイルを置き換えることができるように元のファイルを効果的に保存します。これを元に戻すには、番号を変更して番号10を番号0に複製する必要がありますexec 0<&10(その後、fd 10を閉じますexec 10<&-)。

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


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

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

データに影響を与えるスペースやバックスラッシュなしで行全体をそのまま読み取るには、設定をオフにして使用する必要がIFSありreadます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などの手動ループの代わりにmapfile()を使用してください。readarray

mapfile -t arr < "$filename"

あるいは、一時ファイルがまったくない場合もあります。

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

(そうでない場合、-tmapfile終端はそのまま残ります。)

関連情報