Tag Archives: shell

JSON adatbányászat shellben

TL;DR: ha stock OS X alatt shell scriptben JSON-t akarsz parse-olni, jsc-vel csináld.

A probléma

Van egy Alfred workflow-m, amivel villámgyorsan át tudom állítani a Transmission torrent kliens speed limitjét – ez a script a bele.

Ehhez akartam olyat, hogy amint az Alfred querybe beírom a script filer kulcsszavát, azonnal jelenjen meg, hogy mennyi az aktuális speed limit a Transmission kliensben.

A megoldás: 1/3 (eFi)

Először abban reménykedtem, hogy egy Google search majd azonnal ontja magából a találatokat a “parse JSON in bash”-re, de amiket talált, abból egyik sem tetszett igazán, így aztán benga favágó módjára kikapáltam az engem érdeklő infót, duplán regexpelve (mint az állatok):

#!/bin/sh
## guess Transmission's speed limit

USER=yourUserName
PASS=yourPassword
server=http://yourTransmissionHostIP
SPEED=$1

curlout=$(curl -u $USER:$PASS ${server}:9091/transmission/rpc 2>/dev/null)
regex='(X-Transmission-Session-Id\: )([^<]*)'

if [[ $curlout =~ $regex ]]; then
    sessionid=${BASH_REMATCH[2]}
else
    exit 1
fi

data='{"method": "session-get"}'
R=$(curl -H $USER:$PASS ${server}:9091/transmission/rpc -d "$data" -H "X-Transmission-Session-Id: $sessionid")

ASD_ENABLED=`echo "$R" | grep -o -e '"alt\-speed\-enabled":[a-z]*'`
ASD_ENABLED=`echo "$ASD_ENABLED" | grep -o -e '[a-z]*$'`

ASD=`echo "$R" | grep -o -e '"alt\-speed\-down":[0-9]*'`
ASD=`echo "$ASD" | grep -o -e '[0-9]*$'`

SLD_ENABLED=`echo "$R" | grep -o -e '"speed\-limit\-down\-enabled":[a-z]*'`
SLD_ENABLED=`echo "$SLD_ENABLED" | grep -o -e '[a-z]*$'`

SLD=`echo "$R" | grep -o -e '"speed\-limit\-down":[0-9]*'`
SLD=`echo "$SLD" | grep -o -e '[0-9]*$'`

echo ''
echo ''
echo " "
echo " Set Transmission speed limit"
if [ "$ASD_ENABLED" = "true" ]
then
	echo " Current speed limit: $ASD kbps" 
else
	if [ "$SLD_ENABLED" = "true" ]
	then
		echo " Current speed limit: $SLD kbps" 
	else
		echo " Currently no speed limit is set" 
	fi
fi
echo ' speed-limit.png'
echo ''
echo ''

A megoldás: 2/3 (fds)

Mennyivel szebb lenne ez egy JSON parserrel!
Eltelt pár óra és Fazekas “fds” Dani barátom megjelent, majd kettő perc múlva kirázta a kisujjából ezt:

JSON='{"efi":24,"csokijat":42,"mar":242,"elvittek":666,"fene":[1,2,3,4]}'
/System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Resources/jsc -e "debug(JSON.parse(readline())['fene'][2])" <<< "$JSON"

Script az script, ez meg parsol, úgyhogy ezért INSTANT CSOKI jár.

A megoldás: 3/3 (fds)

fds a posztot látva azt mondta, hogy szerinte nem dolgozott meg a megacsokiért, de ha adnék egy Transmission kimenetet amit a curl leszippant, akkor faragna egy csinosabb verziót. A maximalizmusát már jól ismerve meg nem fosztottam volna a világot az új változattól, úgyhogy azonnal ment az output.
Nem is lenne fds, ha nem csinálta volna meg azonnal jsc, php, python és ruby használatával is az egészet (azért "csak" ennyi interpreterrel, mert ezek érhetők el a gyári OS X alól 2016 januárjában). A hozzám hasonló bolondok biztosan fülig érő szájjal olvassák a scriptet:

#!/bin/bash

## guess Transmission's speed limit
 
USER="yourUserName"
PASS="yourPassword"
server="http://yourTransmissionHostIP"
SPEED="$1"
JSONPARSER="jsc"
 
curlout=$(curl -u $USER:$PASS ${server}:9091/transmission/rpc 2>/dev/null)
regex='(X-Transmission-Session-Id\: )([^<]*)'
 
if [[ $curlout =~ $regex ]]; then
    sessionid=${BASH_REMATCH[2]}
else
    exit 1
fi

data='{"method": "session-get"}'
R=$(curl -H $USER:$PASS ${server}:9091/transmission/rpc -d "$data" -H "X-Transmission-Session-Id: $sessionid")

getargument() {
	local ret
	
	case "$JSONPARSER" in
		jsc)
			ret=$(2>&1 /System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Resources/jsc -e "debug(JSON.parse(readline())['arguments']['$2'])" <<< "$1")
			ret="${ret#-->* }"
			;;
		php)
			ret=$(php -nr "echo json_encode(json_decode(file_get_contents('php://stdin'), true)['arguments']['$2']);" <<< "$1")
			;;
		python)
			ret=$(python -c "import json,sys
print(json.dumps(json.load(sys.stdin)['arguments']['$2']))" <<< "$1")
			;;
		ruby)
			ret=$(ruby -e "require 'json'
puts JSON.parse(STDIN.read)['arguments']['$2']" <<< "$1")
			;;
	esac

	echo "$ret"
}

ASD_ENABLED=$(getargument "$R" "alt-speed-enabled")
ASD=$(getargument "$R" "alt-speed-down")
SLD_ENABLED=$(getargument "$R" "speed-limit-down-enabled")
SLD=$(getargument "$R" "speed-limit-down")

echo ''
echo ''
echo $'\t'''
echo $'\t'$'\t'"Set Transmission speed limit"
if [ "$ASD_ENABLED" = "true" ]; then
	echo $'\t'$'\t'"Current speed limit: $ASD kbps" 
else
	if [ "$SLD_ENABLED" = "true" ]; then
		echo $'\t'$'\t'"Current speed limit: $SLD kbps" 
	else
		echo $'\t'$'\t'"Currently no speed limit is set" 
	fi
fi
echo $'\t'$'\t''speed-limit.png'
echo $'\t'''
echo ''

Persze ennyivel nem érte be, megmérte, hogy melyik verzió mennyi időt zabál el az életemből:

for p in jsc php python ruby; do
	echo -n "$p: "
	JSONPARSER="$p"
	OUT=$(( time -p (
		ASD_ENABLED=$(getargument "$R" "alt-speed-enabled")
		ASD=$(getargument "$R" "alt-speed-down")
		SLD_ENABLED=$(getargument "$R" "speed-limit-down-enabled")
		SLD=$(getargument "$R" "speed-limit-down")
		echo "${ASD_ENABLED}|${ASD}|${SLD_ENABLED}|${SLD}"
	) ) 2>&1)
	echo -n $(tail -n 3 <<< "$OUT" | head -n 1 | cut -d ' ' -f 2)s
	echo ' '$(head -n 1 <<< "$OUT")
done
exit

Az eredmény az, hogy a jsc mindent lever:

jsc: 0.04s false|10|true|500
php: 0.25s false|10|true|500
python: 0.11s false|10|true|500
ruby: 0.23s false|10|true|500

Maximalista grafikusnak megismert Rack barátom szokta azt mondani az árajánlatot sokalló ügyfélnek, hogy "de minden pixel a helyén van". Nos, nekem Dani ugyanez a szerethető fanatikus coderben. Le vagyok nyűgözve, mint mindig :)