USP 友の会 LT (2013/04/13)




 wget と curl で
 並列ダウンロード
         斉藤 博文
           @hi_saito
           hi_saito@yk.rim.or.jp
自己紹介

 斉藤 博文
   日本 GNU AWK ユーザー会主宰
   @hi_saito
   hi_saito@yk.rim.or.jp
   Shell + AWK 最強!
サイトから画像を一括並列ダウンロード

 ひとつひとつの画像をブラウザから右クリッ
  クで保存するのは面倒ですよね。
 画像を抜き出して保存する専用アプリもある
  ようですが、「シェル芸」で十分です。
 LL だと並列ダウンロードするのに fork とか
  を使いますが、使い方が良く分からないとい
  う人もいるんじゃないでしょうか。
使うもの

 bash
 wget
   curl でも可能ですが、今回の場合には
     wget の方が便利です。
 tr
 egrep (grep)
 sort
 sed
 xargs
手順を記述してみる

1. 画像のあるサイトの HTML を落とす。
2. アトリビュートを分割する。
3. 画像を抜き出す。
4. ダブっていたら無駄なのでダブらいないよう
   にする。
5. 必要に応じて絶対 URL に変換する。
6. 並列に分割して画像を落とす。
コードにしてみる
$ wget -O - http://www.usptomo.com/   |
  tr """ "n"                        |
  egrep ".(JPG|PNG|GIF)"             |
  sort -u                             |
  sed 's|^|http://www.usptomo.com|'   |
  xargs -P0 -n1 wget


 xargs で -PN で N 並列実行ですが、-P0
  で可能な限り並列実行します。
 xargs の -nN で N 個単位で分割しますので、
  -n1 だと結果的に全て並列処理になります。
ちょっとしたコツ

 HTML からリンク先を抜き出すには tr で分
  割すると便利な場合が多いです。
 egrep は括弧でグルーピングができるので拡
  張子をまとめて記述することができます。
   もちろん、tr と egrep を awk でまとめ
    て記述することもできます。
並列分割ダウンロード

 巨大なファイルをダウンロードしたい。
 サーバーが 1 接続あたりの回線速度を制限し
  ているため十分な速度が出ていない。
 並列分割ダウンロードする OSS はあまりな
  いよね。
使うもの

 bash
 curl
   今回は wget ではなく curl が必要です。
 sed
 awk
 etc.
手順を記述してみる

1. 対象のファイルサイズを取得する。
2. サイズを分割数で割る。
3. ナンバリングして並列で落とす。
4. 親プロセスは落とし終わるまで待つ。
5. 分割したものを結合する。
対象のファイルサイズを取得
#! /bin/bash
DIV=5
URL=$1
FILE=$(basename ${URL})
PID=$$

CONTENT_LENGTH=$(curl -s --head ${URL}          |
               sed 's|r||'                     |
               awk '/^Content-Length/ {print $2}')


 curl は --head でヘッダーのみを取得でき
  ます。
サイズを分割数で割る
for i in `seq 1 ${DIV}`; do
    START_BYTE=$(echo "${CONTENT_LENGTH} 
                 / ${DIV} * (${i} - 1)" | bc)
    END_BYTE=$(echo   "${CONTENT_LENGTH} 
                 / ${DIV} * ${i} - 1" | bc)

   if [ ${i} -eq ${DIV} ]; then
       END_BYTE=${CONTENT_LENGTH}
   fi


 分割して落とす先頭バイト数と終了バイト数
  を計算します。
ナンバリングして並列で落とす
       TMP_FILE=$(printf "%d-%02d-%s" 
                  ${PID} ${i} ${FILE})

       curl -s --range ${START_BYTE}-${END_BYTE} 
            -o ${TMP_FILE} ${URL} &
done

 curl は --range で落とす範囲を指定するこ
  とができます。
落とし終わるまで待って結合
wait

cat $$-* > ${FILE}

rm -f $$-*


 wait 単独だと全ての子プロセスの終了を待ち
  ます。
USP 友の会 LT (2013/04/13)




  iPhone 用の
カレンダー壁紙を作る
         斉藤 博文
           @hi_saito
           hi_saito@yk.rim.or.jp
使うもの

 bash
   bash の記述を使っています。
 cal
 awk
 convert (ImageMagick)
cal コマンドの基本形
$ cal
       April 2013
Su   Mo Tu We Th Fr   Sa
      1 2 3 4 5        6
 7    8 9 10 11 12    13
14   15 16 17 18 19   20
21   22 23 24 25 26   27
28   29 30


 cal コマンドは何も引数がないと今月のカレ
  ンダーを表示します。
ImageMagick で画像に変換
$ convert -font Ricty-Bold.ttf              
          -pointsize 40                     
          -fill black                       
          -draw "text 120,450 "$(cal)""   
          iphone.png calendar.png

 ImageMagick は
  日本語 TTF も扱え
  ます。
 文字列だけでなく、
  コマンドを埋め込め
  ます。
日曜日だけを抜き出す
$ cal | awk '$0=substr($0, 1, 2)'

Su

 7
14
21
28


 この awk の使い方分かりますか? アクショ
  ンがないのに表示できていますよね。
アクションレスプログラミング

 awk はパターン + アクションですよね。
 でも、アクションがない場合は '{print
   $0}' が省略されたものと見なれます。
 また、awk では基本的に代入は「真」になり
   ます。
$0=substr($0, 1, 2)

                             等価!
{
    $0 = substr($0, 1, 2);
    print $0;
}
土曜日だけを抜き出す
$ cal | awk '$0=sprintf("%20s",substr($0, 19, 2))'


                  Sa
                   6
                  13
                  20
                  27


 これもアクションレスプログラミングですね。
 ここまでくれば、あとは同じです。
最終形態
$ convert -font Ricty-Bold.ttf -pointsize 40             
  -fill black -draw "text 120,450                        
  "$(cal)""                                            
  -fill red   -draw "text 120,450                        
  "$(cal|awk '$0=substr($0, 1, 2)')""                  
  -fill blue -draw "text 120,450                         
  "$(cal|awk '$0=sprintf("%20s",substr($0,19,2))')""   
  iphone.png calendar.png


 ほらね。「シェル
  芸」でできるでしょ。
特定の日付だけを出す方法は?

 特定の日付だけを抜き出すことができれば、
   今日や国民の休日をマーキングすることもで
   きますね。
 土日と比べると複雑ですが、やってみてくだ
$ cal | awk -v day="${DAY}" -v str="@" 
   さい。
'{
     regexp = "(^| )" day "( |$)";
     $0 = gensub(regexp, "1@2", 1, $0);
}
{gsub(/[^@]/, " ")}
{sub(/@/, day)}
1'
最終的には?

 最終的には cron に crontab で登録して毎
  月 1 日に更新し、メールで iPhone に送る
  ようにすると便利です。

USP 友の会 LT 資料 20130413