PowerShellにパスワード文字列を直接書かない手法


PowerShellで用意されているGet-Credential 関連を駆使して、PowerShellスクリプト内に直接パスワードを書かないで済む手法を作成した。

スクリプトを配置したディレクトリにscript.cred というファイルを作り、そこに暗号化されたパスワード文字列を配置する、という仕組み。

$authfile=$PSScriptRoot+".\script.cred"
$username="admin" #ユーザ名
$passwdstr="" # この変数にパスワード文字列が入る。
if((Test-Path $authfile) -eq $false){
    $creds = Get-Credential -UserName $username -Message $($username+"ユーザのパスワードを入力してください")
    $creds.Password | ConvertFrom-SecureString | Set-Content -Path $authfile | Out-Null
    $passwdstr=[Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($creds.Password))
}else{
    $passwdstr=[Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($(Get-Content $authfile | ConvertTo-SecureString)))
}

# この段階の $passwdstr には暗号化されていないパスワード文字列が入っている

なお、メモリ上の$passwdstr には暗号化されていない文字列が保存されているため、そこは注意が必要。

CommvaultのREST APIをPowerShellで使って見る


Commvaultバックアップの操作にREST APIを使うことができる。

・Commvault V11SP24の「REST API Overview
Commvault Rest APIの出力サンプル群

ログインして、指定したクライアントのサブクライアント情報を取得するPowerShellで作成してみた。

# CommCellユーザ情報
$restapiuser="admin"
$restapipasswd="パスワード"

# REST APIで使用するURL
# どちらを使うかについて https://documentation.commvault.com/11.24/expert/45592_available_web_services_for_rest_api.html
#$restapiurlbase="http://~:81/SearchSvc/CVWebService.svc/"
$restapiurlbase="http://~/webconsole/api/"

# 検索用文字列
$clientname="クライアント名"

# CommCell環境にログイン
$restapipasswdbase64=[Convert]::ToBase64String(([System.Text.Encoding]::Default).GetBytes($restapipasswd))
$headers=@{
    "Accept"="application/json"
    "Content-Type"="application/json"
}
$loginReq = @{
    username=$restapiuser
    password=$restapipasswdbase64
}
$loginresponse=Invoke-RestMethod -Method post -Uri $($restapiurlbase+"Login") -Body $($loginReq|ConvertTo-Json) -ContentType 'application/json' -Headers $headers

# 指定クライアントのサブクライアント情報取得
$headers=@{
    "Accept"="application/json"
    "Authtoken"=$loginresponse.token
}
$response=Invoke-RestMethod -Method Get -Uri $($restapiurlbase+"Subclient?clientName="+$clientname) -Headers $headers

# $response.subClientProperties.subClientEntity にサブクライアントの情報が入っているが
# 複数のサブクライアントがあると $response.subClientProperties.subClientEntity.subclientName が複数行になるので注意

# 表示
$response.subClientProperties.subClientEntity | ForEach-Object {
    $subcliententory=$_
    Write-host $subcliententory
 }

# CommCellログアウト
$headers=@{
    "Accept"="application/json"
    "Authtoken"=$loginresponse.token
}
$logoutresponse=Invoke-RestMethod -Method post -Uri $($restapiurlbase+"Logout") -Headers $headers

ただ、このスクリプトを使うとCommvault Event viewerに表示されるログイン情報が「Machine:unknown Locale:unknown」となる。

これを設定できるか調べて見たが、無いように思える。

TeraTermマクロでコマンド出力結果を使って別のコマンドを実行する


NetAppの設定をTeraTermマクロで取得出来るようにマクロを作成中し https://github.com/osakanataro/get-ontap-config で公開中です。

物理ディスク一覧を作成する際、単純に「storage disk show」で取得すると、2ノードの表示が入り交じり、どちらに所属している物理ディスクなのか判別が面倒。

なので、「storage disk show -nodelist ノード名」という形で出力を分けたいのだが、NetAppに対して「system node show -fields node」で取得したノード一覧からノード名を抜き出して、それぞれに対してコマンドを実行するためには、TeraTermマクロで実装できるのかを調査した。

調査

まず、NetAppのノード名一覧の出力を確認

netapp01::> system node show -fields node
node
-------------
netapp01-01
netapp01-02
2 entries were displayed.

netapp01::>

ノードとして「netapp01-01」と「netapp01-02」があることを確認。

また、system node showコマンドの出力のうち最初の2行はヘッダ、そして、ノード名出力後に「~ entries were displayed.」と出力がある。これらは使わないので除外が必要。

スクリプト

サンプルとして、ノード一覧を表示して、各ノードに対して「system node run -node ノード名 hostname」を実行する、というものを作成した。

setsync 1
timeout=60
strdim nodenames 20
I=0
sendln 'system node show -fields node'
recvln ; コマンド入力 のecho出力  をスキップ
recvln ; ヘッダ部分 をスキップ
recvln ; --- 部分 をスキップ

endflag=1
while endflag=1
	recvln
	strscan inputstr 'displayed.'
	if result>0 then
		endflag=0
	else
		nodenames[I] = inputstr
		I=I+1
	endif
endwhile
wait '::> '

J=0
while I > J
	sendln 'system node run -node ' nodenames[J] ' hostname'
	wait '::> '
	J=J+1
endwhile

setsync 0

スクリプトの解説

今回のようなコマンド出力結果を受け取る場合は、「setsync 1」を実行して、確実に出力結果を受け取る設定(同期)とする必要がある。

次に「recvln」する際、TeraTermマクロからNetAppに送信した文字列もrecvlnで受け取ることになっているため、最初の1行は捨てている。(recvlnして、その後、何の処理もしていない)

続けて2行がヘッダなので、「recvln」で2行分受け取り、何も処理していない。

「2 entries were displayed.」というエントリ以降の行は不要なので、whileを終了する処理を入れるが、1台だったりすると1 entry wasとかになったりしそうなのでdisplayed.だけで終了検出。

whileを抜けたあとは、コマンドプロンプトの「::> 」が来るまで待ちを入れる。

スクリプト実行例

netapp01::> system node show -fields node
node
-------------
netapp01-01
netapp01-02
2 entries were displayed.

netapp01::> system node run -node netapp01-01 hostname
netapp01-01
netapp01::> system node run -node netapp01-02 hostname
netapp01-02
netapp01::>

はてなブログからwordpressへぶっこぬきで移行した話


現在はてなブログで運用しているけど、画像付きでのエクスポートができない、とかいう話を聞いた。

ローカルに静的コンテンツとして取り込もう

とりあえず、本文と画像をダウンロードすればいいだろう、と、まずは簡単に下記でコンテンツ取得をかけた。

wget --mirror \
    --page-requisites \
    --quiet --show-progress \
    --no-parent \
    --convert-links \
    --adjust-extension \
    --execute robots=off \
    https://retoge-mag.hatenablog.com/

適当なWebサーバに配置して見てみると、HTMLのみで、そしてサイト内リンクもhttpsからフルで指定されているタイプだったので、URL書き換えを雑に実施。

他にもHTML書き換えが必要だろう、ということでダウンロードしてきたやつではなく、閲覧用のHTMLを別に用意することにして、下記の様に実行した。

cp -r retoge-mag.hatenablog.com viewpage

for file in `find ./viewpage -name \*.html -print`
do
 sed -i -e "s/retoge-mag.hatenablog.com/retoge-mag.websa.jp\/old\/viewpage/g" $file
done

これでまずはHTMLのみがコピーされた状態だが、リンクをいくつかクリックしてみると404 not foundとなるものがある。

日付ごとのまとめはダウンロードされていないが、これについては無視することとした。

個別記事のリンクで、末尾が数字で終わっているものがいくつかあり、それで404が発生していた。それらのコンテンツは数字.htmlという形でダウンロードされていたので .htaccess に下記の設定を行った。

<IfModule mod_rewrite.c>
RewriteEngine On

RewriteCond     %{REQUEST_FILENAME} !-f
RewriteRule     ^(.*)$ $1.html [L]

</IfModule>

次に画像のダウンロードを行った。

HTMLからhttp か httpsで始まるリンクっぽいものを集めてみると画像は st-hatena.com というドメイン名の所に置いてあるようだった。

なので、下記スクリプトを実行して、該当するURLを全てダウンロードしてきた。

for file in `find ./viewpage -name \*.html -print`
do
 grep -oE 'http(s?)://[0-9a-zA-Z?=#+_&:;/.%\-]+' $file >> urllists.txt
done
sort urllists.txt | uniq > urllists2.txt
grep st-hatena.com urllists2.txt > urllists3.txt
wget --mirror     --page-requisites     --quiet --show-progress     --no-parent     --convert-links     --adjust-extension -i urllists3.txt

なお、最初は「grep -oE ‘http(s?)://[0-9a-zA-Z?=#+_&:/.%-]+’ $file >> urllists.txt」だったのだが、縮小処理をかけた画像のURLに;が含まれていたので、追加している。

画像はローカルにダウンロードできたものの、HTMLの方は書き換わっていないため、以下で書き換えを実施した。

for file in `find ./viewpage -name \*.html -print`
do
 sed -i -e "s/b.st-hatena.com/retoge-mag.websa.jp\/old\/b.st-hatena.com/g" $file
 sed -i -e "s/cdn-ak.f.st-hatena.com/retoge-mag.websa.jp\/old\/cdn-ak.f.st-hatena.com/g" $file
 sed -i -e "s/cdn.blog.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.blog.st-hatena.com/g" $file
 sed -i -e "s/cdn.pool.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.pool.st-hatena.com/g" $file
 sed -i -e "s/cdn.profile-image.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.profile-image.st-hatena.com/g" $file
 sed -i -e "s/cdn.user.blog.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.user.blog.st-hatena.com/g" $file
 sed -i -e "s/usercss.blog.st-hatena.com/retoge-mag.websa.jp\/old\/usercss.blog.st-hatena.com/g" $file
done

これで、見栄えは悪いものの画像ファイル含めてローカルにコンテンツ自体はコピーされた。

WordPressに取り込もう

あまりにも見栄えが悪いので、やっぱりWordpressに取り込むべきか、とインポート用XMLファイルを作ることにする。

WordPressのサイトを調べたのだがインポート用XMLファイルの仕様がよくわからなかった。

まず、Wordpressで適当に記事とカテゴリを作成して、それをエクスポートしたXMLを作成した。

結構いらない要素が多い感じがしたので最小限の記述はなにか探したところ「Simple WordPress Import Structure XML Example」を発見。

<?xml version="1.0" encoding="UTF-8" ?>

<rss version="2.0"
	xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:wp="http://wordpress.org/export/1.2/"
>

<channel>
	<wp:wxr_version>1.2</wp:wxr_version>
	<wp:author></wp:author>

	<item>
		<title>Test 1</title>
		<description></description>
		<content:encoded><![CDATA[Test Post 1]]></content:encoded>
		<excerpt:encoded><![CDATA[]]></excerpt:encoded>
		<wp:status>publish</wp:status>
		<wp:post_type>post</wp:post_type>
		<category domain="post_tag" nicename="redy"><![CDATA[redy]]></category>
		<category domain="category" nicename="teory"><![CDATA[Teory]]></category>
		<category domain="category" nicename="tree"><![CDATA[Tree]]></category>
	</item>
	<item>
		<title>Test 2</title>
		<description></description>
		<content:encoded><![CDATA[Test Post 2]]></content:encoded>
		<excerpt:encoded><![CDATA[]]></excerpt:encoded>
		<wp:status>publish</wp:status>
		<wp:post_type>post</wp:post_type>
		<category domain="category" nicename="test-category"><![CDATA[Test Category]]></category>
	</item>
</channel>
</rss>

これをベースに作成することにした。

はてなブログが出力したHTMLから下記の要素を抜き出してXMLタグを出力する test.plを作成した。

#!/bin/perl
#

use strict;
use warnings;

my $sourcefile=$ARGV[0];

if(!-f $sourcefile){
        print "file not found\n";
        exit 1;
}

my $i=0;
my @bodys;

open(FILE,$sourcefile);
while(<FILE>){
        $bodys[$i]=$_;
        $i++;
}
close(FILE);

my $maxline=$i;
my $line;

my ($st,$ed,$tmp);

my $createdate="";
my $modifydate="";
my $title="";
my $categorys="";
my $categoryssub="";
my $categorysterm="";
my $author="";
my $contents="";
my $description="";

$i=0;
while($i<$maxline){
        $line=$bodys[$i];
        if(($line =~ /<time datetime=/)&&($createdate eq "")){
                # 作成日取得
                # 下の方に他のページのdatetimeもあるので1個目だけ採用
                $st=index($line,"datetime=");
                $st=index($line,"\"",$st)+length("\"");
                $ed=index($line,"\"",$st);
                $createdate=substr($line,$st,$ed-$st);
        #}elsif(($line =~ /entry-footer-time/)&&($line =~ /class=\"updated\"/)){
        }elsif($line =~ /dateModified/){
                # 更新日取得
                $st=index($line,"dateModified");
                $st=index($line,":",$st);
                $st=index($line,"\"",$st)+length("\"");
                $ed=index($line,"\"",$st);
                $modifydate=substr($line,$st,$ed-$st);
        }elsif($line =~ /entry-title-link/){
                # タイトル取得
                $st=index($line,">")+length(">");
                $ed=index($line,"</",$st);
                $title=substr($line,$st,$ed-$st);
        }elsif($line =~ /entry-categories/){
                # カテゴリ取得
                my $flag=0;
                while($flag==0){
                        $i++;
                        $line=$bodys[$i];
                        if($line =~/<\/div>/){
                                # カテゴリ終了
                                $flag=1;
                        }elsif($line =~ /entry-category-link/){
                                $st=index($line,">")+length(">");
                                $ed=index($line,"</",$st);
                                $tmp=substr($line,$st,$ed-$st);
                                $categorys.="<category domain=\"category\" nicename=\"".$tmp."\" ><![CDATA[".$tmp."]]></category>";
                        }
                }
        }elsif($line =~ /author vcard/){
                # 作者取得
                $ed=index($line,"</");
                $st=rindex($line,">",$ed)+length(">");
                $author=substr($line,$st,$ed-$st);
        }elsif($line =~ /entry-content/){
                # 本文取得
                my $flag=0;
                while($flag==0){
                        $i++;
                        $line=$bodys[$i];
                        if($line =~/entry-footer/){
                                # 本文終了
                                $flag=1;
                        }else{
                                $contents.=$line;
                        }
                }
        }elsif($line =~ /og:description/){
                # description取得
                $st=index($line,"content=\"")+length("content=\"");
                $ed=index($line,"\"",$st);
                $description=substr($line,$st,$ed-$st);
        }


        $i++;
}
# URL変換処理
$contents =~ s/b.st-hatena.com/retoge-mag.websa.jp\/old\/b.st-hatena.com/g;
$contents =~ s/cdn-ak.f.st-hatena.com/retoge-mag.websa.jp\/old\/cdn-ak.f.st-hatena.com/g;
$contents =~ s/cdn.blog.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.blog.st-hatena.com/g;
$contents =~ s/cdn.pool.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.pool.st-hatena.com/g;
$contents =~ s/cdn.profile-image.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.profile-image.st-hatena.com/g;
$contents =~ s/cdn.user.blog.st-hatena.com/retoge-mag.websa.jp\/old\/cdn.user.blog.st-hatena.com/g;
$contents =~ s/usercss.blog.st-hatena.com/retoge-mag.websa.jp\/old\/usercss.blog.st-hatena.com/g;

#


print "<item>\n";
print "<description>".$description."</description>\n";
print "<excerpt:encoded><![CDATA[]]></excerpt:encoded>\n";
print "<wp:status>publish</wp:status>\n";
print "<wp:post_type>post</wp:post_type>\n";
print "<wp:post_date_gmt>".$createdate."</wp:post_date_gmt>\n";
#print "<wp:post_modified_gmt>".$modifydate."</wp:post_modified_gmt>\n";
print "<wp:post_modified>".$modifydate."</wp:post_modified>\n";
print "<title><![CDATA[".$title."]]></title>\n";
print $categorys."\n";
#print "<dc:creator><![CDATA[".$author."]]></dc:creator>\n";
print "<dc:creator><![CDATA[pc88multi2]]></dc:creator>\n";
#print "<content:encoded><![CDATA[<!-- wp:paragraph -->\n".$contents."<!-- /wp:paragraph -->]]></content:encoded>\n";
print "<content:encoded><![CDATA[".$contents."]]></content:encoded>\n";
print "</item>\n";

このtest.pl は単体ファイルに対してのみ実行されるので、対象ファイル群に対して実行するために下記の別スクリプトを用意して実行した。

echo '<?xml version="1.0" encoding="UTF-8" ?>' > wordpress.xml
echo '<rss version="2.0" xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"  xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:wp="http://wordpress.org/export/1.2/" >' >> wordpress.xml

echo '<channel><wp:wxr_version>1.2</wp:wxr_version>' >> wordpress.xml
echo '<wp:author></wp:author>' >> wordpress.xml

for file in `find retoge-mag.hatenablog.com/entry/* -name \*.html`
do echo --- $file ---
        ./test.pl $file >> wordpress.xml
done

echo '</channel>' >> wordpress.xml
echo '</rss>' >> wordpress.xml

こうして出力されたwordpress.xmlをWordpressにてインポートすることで取り込みは完了した。

なお、画像ファイルについてはXMLへどうかけばいいのかが分からなかったため、ローカルにコピーした際のURLをそのまま利用し、Wordpress管理下には置かなかった。


2021/11/30追記

この手法で取り込むとWordpress上は「クラシック」形式での状態となっていた。

各記事で「ブロックへ変換」を行ったあと

各画像でを選択し「↑」ボタンをクリックすると画像がWordpress管理下にあるwp-contentsディレクトリ内にダウンロードされ、Wordpressで管理できるようになった。

画像

pythonのtwitterモジュールで特定ユーザの発言を取得する


twitterのspaceによる音声配信は、m3u8ファイルを使用してのhttpsによるaacファイルの配布という形で実現されている。

streamlinkにm3u8ファイルのURLを与えると音声ファイルが取得できるので、これを自動化できないか検討している。

まず第1歩として、指定したユーザのタイムラインでspace配信のURLがあった場合に検出できないかを確認。

単純にwgetやcurlで「curl -s https://twitter.com/niselog/」とやっても発言は拾えない。

python-twitterのマニュアルを見ながらpythonスクリプトを作成

#!/usr/bin/python

import os
import twitter

token='文字列'
token_secret='文字列'
consumer_key = '文字列'
consumer_secret='文字列'

t = twitter.Api(consumer_key=consumer_key,
        consumer_secret=consumer_secret,
        access_token_key=token,
        access_token_secret=token_secret)

for line in t.GetUserTimeline(screen_name="niselog"):
        print line

これを実行すると、下記の様な出力になる

-bash-4.2$ ./test3.py |tail -2
{"created_at": "Sat May 14 08:13:22 +0000 2016", "hashtags": [{"text": "\u30b1\u30eb\u30d9\u30ed\u30b9\u30d6\u30ec\u30a4\u30c9"}], "id": 731396977758339072, "id_str": "731396977758339072", "lang": "ja", "source": "<a href=\"https://about.twitter.com/products/tweetdeck\" rel=\"nofollow\">TweetDeck</a>", "text": "\u507d\u30ed\u30b0 for #\u30b1\u30eb\u30d9\u30ed\u30b9\u30d6\u30ec\u30a4\u30c9 \u306f\u30b1\u30eb\u30d9\u30ed\u30b9\u8d85\u4f1a\u8b70\u306e\u4e00\u89a7\u30da\u30fc\u30b8\u306b\u5bfe\u5fdc\u3057\u307e\u3057\u305f https://t.co/gDlRyiFWC3", "urls": [{"expanded_url": "http://tw5.niselog.jp/", "url": "https://t.co/gDlRyiFWC3"}], "user": {"created_at": "Mon Mar 01 05:51:23 +0000 2010", "default_profile": true, "default_profile_image": true, "description": "\u30c8\u30df\u30fc\u30a6\u30a9\u30fc\u30ab\u30fcPBW \u7121\u9650\u306e\u30d5\u30a1\u30f3\u30bf\u30b8\u30a2/\u30b7\u30eb\u30d0\u30fc\u30ec\u30a4\u30f3/\u30a8\u30f3\u30c9\u30d6\u30ec\u30a4\u30ab\u30fc\uff01/\u30b5\u30a4\u30ad\u30c3\u30af\u30cf\u30fc\u30c4\u3078\u306e\u643a\u5e2f\u5411\u3051\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u30b5\u30fc\u30d3\u30b9\u300c\u507d\u30ed\u30b0\u300d\u306e\u7ba1\u7406\u8005\u30a2\u30ab\u30a6\u30f3\u30c8\u3067\u3059\u3002 http://t.co/QMKlQjzjSI http://t.co/YEiNJUvGdU", "followers_count": 40, "id": 118604209, "id_str": "118604209", "listed_count": 5, "name": "niselog \u7ba1\u7406\u8005", "profile_background_color": "C0DEED", "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_image_url": "http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", "profile_image_url_https": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", "profile_link_color": "1DA1F2", "profile_sidebar_border_color": "C0DEED", "profile_sidebar_fill_color": "DDEEF6", "profile_text_color": "333333", "profile_use_background_image": true, "screen_name": "niselog", "statuses_count": 340, "url": "http://t.co/QMKlQjzjSI", "withheld_in_countries": []}, "user_mentions": []}
{"created_at": "Sat May 14 07:02:08 +0000 2016", "hashtags": [], "id": 731379051852505088, "id_str": "731379051852505088", "lang": "ja", "retweet_count": 2, "source": "<a href=\"https://about.twitter.com/products/tweetdeck\" rel=\"nofollow\">TweetDeck</a>", "text": "\u7121\u9650\u306e\u30d5\u30a1\u30f3\u30bf\u30b8\u30a2\u3001\u30b7\u30eb\u30d0\u30fc\u30ec\u30a4\u30f3\u3001\u30a8\u30f3\u30c9\u30d6\u30ec\u30a4\u30ab\u30fc\u5411\u3051\u306e\u507d\u30ed\u30b0 https://t.co/QMKlQjzjSI \u3067\u3059\u304c\u3001\u590f\u9803\u306b\u5b8c\u5168\u505c\u6b62\u3059\u308b\u4e88\u5b9a\u3067\u3059\u3002(URL\u306f\u6b8b\u308a\u307e\u3059\u304c)", "urls": [{"expanded_url": "http://niselog.jp/", "url": "https://t.co/QMKlQjzjSI"}], "user": {"created_at": "Mon Mar 01 05:51:23 +0000 2010", "default_profile": true, "default_profile_image": true, "description": "\u30c8\u30df\u30fc\u30a6\u30a9\u30fc\u30ab\u30fcPBW \u7121\u9650\u306e\u30d5\u30a1\u30f3\u30bf\u30b8\u30a2/\u30b7\u30eb\u30d0\u30fc\u30ec\u30a4\u30f3/\u30a8\u30f3\u30c9\u30d6\u30ec\u30a4\u30ab\u30fc\uff01/\u30b5\u30a4\u30ad\u30c3\u30af\u30cf\u30fc\u30c4\u3078\u306e\u643a\u5e2f\u5411\u3051\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4\u30b5\u30fc\u30d3\u30b9\u300c\u507d\u30ed\u30b0\u300d\u306e\u7ba1\u7406\u8005\u30a2\u30ab\u30a6\u30f3\u30c8\u3067\u3059\u3002 http://t.co/QMKlQjzjSI http://t.co/YEiNJUvGdU", "followers_count": 40, "id": 118604209, "id_str": "118604209", "listed_count": 5, "name": "niselog \u7ba1\u7406\u8005", "profile_background_color": "C0DEED", "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_image_url": "http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", "profile_image_url_https": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", "profile_link_color": "1DA1F2", "profile_sidebar_border_color": "C0DEED", "profile_sidebar_fill_color": "DDEEF6", "profile_text_color": "333333", "profile_use_background_image": true, "screen_name": "niselog", "statuses_count": 340, "url": "http://t.co/QMKlQjzjSI", "withheld_in_countries": []}, "user_mentions": []}
-bash-4.2$

unicode escapeという形式で文字が表示されているので読めない。

全体的にみてみるとJSON形式のデータになっているのでjqコマンドを通して見た。

-bash-4.2$ ./test3.py |tail -2|jq .
{
  "created_at": "Sat May 14 08:13:22 +0000 2016",
  "hashtags": [
    {
      "text": "ケルベロスブレイド"
    }
  ],
  "id": 731396977758339100,
  "id_str": "731396977758339072",
  "lang": "ja",
  "source": "<a href=\"https://about.twitter.com/products/tweetdeck\" rel=\"nofollow\">TweetDeck</a>",
  "text": "偽ログ for #ケルベロスブレイド はケルベロス超会議の一覧ページに対応しました https://t.co/gDlRyiFWC3",
  "urls": [
    {
      "expanded_url": "http://tw5.niselog.jp/",
      "url": "https://t.co/gDlRyiFWC3"
    }
  ],
  "user": {
    "created_at": "Mon Mar 01 05:51:23 +0000 2010",
    "default_profile": true,
    "default_profile_image": true,
    "description": "トミーウォーカーPBW 無限のファンタジア/シルバーレイン/エンドブレイカー!/サイキックハーツへの携帯向けゲートウェイサービス「偽ログ」の管理者 アカウントです。 http://t.co/QMKlQjzjSI http://t.co/YEiNJUvGdU",
    "followers_count": 40,
    "id": 118604209,
    "id_str": "118604209",
    "listed_count": 5,
    "name": "niselog 管理者",
    "profile_background_color": "C0DEED",
    "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
    "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
    "profile_image_url": "http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png",
    "profile_image_url_https": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png",
    "profile_link_color": "1DA1F2",
    "profile_sidebar_border_color": "C0DEED",
    "profile_sidebar_fill_color": "DDEEF6",
    "profile_text_color": "333333",
    "profile_use_background_image": true,
    "screen_name": "niselog",
    "statuses_count": 340,
    "url": "http://t.co/QMKlQjzjSI",
    "withheld_in_countries": []
  },
  "user_mentions": []
}
{
  "created_at": "Sat May 14 07:02:08 +0000 2016",
  "hashtags": [],
  "id": 731379051852505100,
  "id_str": "731379051852505088",
  "lang": "ja",
  "retweet_count": 2,
  "source": "<a href=\"https://about.twitter.com/products/tweetdeck\" rel=\"nofollow\">TweetDeck</a>",
  "text": "無限のファンタジア、シルバーレイン、エンドブレイカー向けの偽ログ https://t.co/QMKlQjzjSI ですが、夏頃に完全停止する予定です。(URLは残りますが)",
  "urls": [
    {
      "expanded_url": "http://niselog.jp/",
      "url": "https://t.co/QMKlQjzjSI"
    }
  ],
  "user": {
    "created_at": "Mon Mar 01 05:51:23 +0000 2010",
    "default_profile": true,
    "default_profile_image": true,
    "description": "トミーウォーカーPBW 無限のファンタジア/シルバーレイン/エンドブレイカー!/サイキックハーツへの携帯向けゲートウェイサービス「偽ログ」の管理者 アカウントです。 http://t.co/QMKlQjzjSI http://t.co/YEiNJUvGdU",
    "followers_count": 40,
    "id": 118604209,
    "id_str": "118604209",
    "listed_count": 5,
    "name": "niselog 管理者",
    "profile_background_color": "C0DEED",
    "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
    "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
    "profile_image_url": "http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png",
    "profile_image_url_https": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png",
    "profile_link_color": "1DA1F2",
    "profile_sidebar_border_color": "C0DEED",
    "profile_sidebar_fill_color": "DDEEF6",
    "profile_text_color": "333333",
    "profile_use_background_image": true,
    "screen_name": "niselog",
    "statuses_count": 340,
    "url": "http://t.co/QMKlQjzjSI",
    "withheld_in_countries": []
  },
  "user_mentions": []
}
-bash-4.2$

今度は可読できる状態になった。

で・・・twitter spaceの場合、次のような出力になっていた

{
  "created_at": "Fri Aug 13 02:11:56 +0000 2021",
  "hashtags": [],
  "id": ~,
  "id_str": "~",
  "lang": "ja",
  "source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>",
  "text": "スペース配信開始\nhttps://t.co/~",
  "urls": [
    {
      "expanded_url": "https://twitter.com/i/spaces/~",
      "url": "https://t.co/~"
    }
  ],
  "user": {
<略>
 }
}

urlsにexpanded_urlという項目があり、そこに /i/spaces/~ というtwitter spaceのURLが書かれているという状態であった。

とりあえず、これで、twitter spaceに参加するためのURLは取得できそう。

jqコマンドのオプションを変えて.urlsだけを抜き出して見る

-bash-4.2$ ./test3.py|jq '.urls'
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[
  {
    "expanded_url": "https://twitter.com/i/spaces/~",
    "url": "https://t.co/~"
  }
]
[
  {
    "expanded_url": "https://~",
    "url": "https://t.co/~"
  }
]
[]
[]
[]
[]
[]
[]
[]
[]
-bash-4.

中身が入ってないものまで表示されてしまう。

jq コマンドを使う日常のご紹介」になかなかいい参考事例を発見

-bash-4.2$ ./test3.py|jq -r '.urls[] '
{
  "expanded_url": "https://twitter.com/i/spaces/~",
  "url": "https://t.co/~"
}
{
  "expanded_url": "https://~",
  "url": "https://t.co/~"
}
-bash-4.2$

これでURLがある場合だけ結果が出た。

もう1歩すすめてjqコマンドマニュアルみつつselectで完全一致だけ出力

-bash-4.2$ ./test3.py|jq -r '.urls[] |select(.expanded_url == "https://twitter.com/i/spaces/~")'
{
  "expanded_url": "https://twitter.com/i/spaces/~",
  "url": "https://t.co/~"
}
-bash-4.2$

部分一致を探すとstartswith,endswithで指定文字列で始まる場合/終わる場合を条件にできた

-bash-4.2$ ./test3.py|jq -r '.urls[] | select(.expanded_url | startswith("https://twitter.com/i/spaces/"))'
{
  "expanded_url": "https://twitter.com/i/spaces/~",
  "url": "https://t.co/~"
}
-bash-4.2$

含まれる場合は containsでいけた

-bash-4.2$ ./test3.py|jq -r '.urls[] | select(.expanded_url | contains("https://
twitter.com/i/spaces/"))'
{
  "expanded_url": "https://twitter.com/i/spaces/~",
  "url": "https://t.co/~"
}
-bash-4.2$