Skip to content

Instantly share code, notes, and snippets.

@Hayao0819
Created Apr 22, 2021
Embed
What would you like to do?
Linuxにインストールされてるアプリの一覧をJsonで出力するスクリプト
#!/usr/bin/env bash
set -eu
AppDir="/usr/share/applications"
DesktopFileExt="desktop"
function getDesktopFile(){
#grep -E "^${2}" "${1}" | cut -d "=" -f 2 | tr -d "\n"
_Result="$(crudini --get "${1}" "Desktop Entry" "${2}")"
_Result="$(echo ${_Result} | tr -d "\"")"
if echo "${_Result}" | grep -q "^[0-9]\+$" || [[ "${_Result}" = true ]] || [[ "${_Result}" = false ]]; then
echo -n "${_Result}"
else
echo -n "\"${_Result}\""
fi
}
# Load AppList
while read -r app; do
AppList+=("${app}")
done < <(find "${AppDir}" -maxdepth 1 -mindepth 1 -name "*.${DesktopFileExt}" -printf "%f\n" 2> /dev/null | sed "s|.${DesktopFileExt}$||g" | sort)
JSON="{}"
Count=0
for _App in "${AppList[@]}"; do
Count=$(( Count + 1 ))
echo "Loading ${_App} ... ${Count}/${#AppList[@]} $(awk "BEGIN { print ${Count} * 100 /${#AppList[@]}}")%" >&2
_JsonName="$(echo -n "${_App}" | tr "." "_" | tr "-" "_")"
_DesktopFilePath="${AppDir}/${_App}.${DesktopFileExt}"
_setValueToJson(){
JSON="$(echo "${JSON}" | jq -c ".${_JsonName}.${1} = $(getDesktopFile "${_DesktopFilePath}" "${1}")")"
}
JSON="$(echo "${JSON}" | jq -c ".${_JsonName} = {}")"
_setValueToJson "Name"
_setValueToJson "Exec"
_setValueToJson "iCON"
_setValueToJson "Type"
_setValueToJson "Comment"
done
echo "${JSON}" | jq
@Hayao0819
Copy link
Author

Hayao0819 commented Apr 22, 2021

依存関係: crudini jq bash
どちゃくそ遅いので注意

@ko1nksm
Copy link

ko1nksm commented Apr 27, 2021

@Hayao0819 Twitter で DM しましたとおり現在記事を書いておりますが、もしかしたら必要かと思い先にリファクタリングしたコードをお渡しします。

以下は crudinijq を残しつつ一般的なリファクタリングを行ったコードです。記事のコードよりバグ修正して少し改善しています。私の環境(/usr/share/applications 以下のファイル数 10個、WSL2上)で 4.5 秒 → 0.75 秒 と 6 倍ほど高速化しました。ダブルクォートの部分は必要性がよくわからず削除していますので注意してください。

#!/usr/bin/env bash
set -eu
AppDir="/usr/share/applications"
DesktopFileExt="desktop"

log() {
    awk 'BEGIN { printf "Loading %s ... %d/%d %.2f\n", ARGV[1], ARGV[2], ARGV[3], ARGV[2] * 100 / ARGV[3] }' "${@}"
}

getDesktopFile() {
    grep -E "^(${1})=|^\[" "${2}" | crudini --get --format sh - "Desktop Entry"
}

# Load AppList
readarray -t AppList < <(find "${AppDir}" -maxdepth 1 -mindepth 1 -name "*.${DesktopFileExt}" | sort)

Count=0
for _DesktopFilePath in "${AppList[@]}"; do
    Count=$(( Count + 1 ))
    _App="${_DesktopFilePath##*/}" && _App="${_App%%.*}"
    JsonName="${_App//[.-]/_}"
    log "${_App}" "${Count}" "${#AppList[@]}" >&2

    Name="" Exec="" Icon="" Type="" Comment=""
    eval "$(getDesktopFile "Name|Exec|Icon|Type|Comment" "${_DesktopFilePath}")"

    jq -n '{($JsonName): {$Name, $Exec, $Icon, $Type, $Comment}}' \
        --arg JsonName "${JsonName}" \
        --arg Name "${Name}" \
        --arg Exec "${Exec}" \
        --arg Icon "${Icon}" \
        --arg Type "${Type}" \
        --arg Comment "${Comment}"
done | jq -s add

以下は crudinijq などを取り除いて速度重視でシェルスクリプトのみで実装したコードです。コードは長くなっていますが 4.5 秒 → 17 ミリ秒 と 260 倍ほど高速化しています。やりすぎ感たっぷりなので必要な部分を選択して使ってください。

#!/usr/bin/env bash
set -eu
AppDir="/usr/share/applications"
DesktopFileExt="desktop"

escape() {
    tmp="${2}"
    tmp="${tmp//\\/\\\\}"
    tmp="${tmp//\"/\\\"}"
    tmp="${tmp//\'/\\\'}"
    tmp="${tmp//\//\\\/}"
    tmp="${tmp//$'\b'/\\b}"
    tmp="${tmp//$'\f'/\\f}"
    tmp="${tmp//$'\n'/\\n}"
    tmp="${tmp//$'\r'/\\r}"
    tmp="${tmp//$'\t'/\\t}"
    printf -v "${1}" '%s' "${tmp}"
}

log() {
    rate=$((${2} * 10000 / ${3})) && n=$((rate / 100)) && f=$((100 + rate % 100)) && f="${f#1}"
    echo "Loading ${1} ... ${2}/${3} ${n}.${f}%"
}

readDesktopEntry() {
    Name="" Exec="" Icon="" Type="" Comment="" in_section=''
    readarray -t lines
    for line in "${lines[@]}"; do
        case "${line}" in
            "[Desktop Entry]") in_section=1 && continue ;;
            "["*) in_section=''&& continue ;;
        esac
        [ "${in_section}" ] || continue
        case "${line%%=*}" in (Name | Exec | Icon | Type | Comment)
            printf -v "${line%%=*}" '%s' "${line#*=}"
        esac
    done
}

set -- "${AppDir}/"*".${DesktopFileExt}"
[ -e "$1" ] || set --

Count=0
echo '{'
for _DesktopFilePath in "${@}"; do
    [ "${Count}" -gt 0 ] && echo ","
    Count=$(( Count + 1 ))
    _App="${_DesktopFilePath##*/}" && _App="${_App%%.*}"
    JsonName="${_App//[.-]/_}"
    log "${_App}" "${Count}" "${#@}" >&2

    readDesktopEntry < "${_DesktopFilePath}"

    escape JsonName "${JsonName}"
    escape Name "${Name}"
    escape Exec "${Exec}"
    escape Icon "${Icon}"
    escape Type "${Type}"
    escape Comment "${Comment}"

    printf '"%s": {"Name": "%s", "Exec": "%s", "Icon": "%s", "Type": "%s", "Comment": "%s"}\n' \
      "${JsonName}" "${Name}" "${Exec}" "${Icon}" "${Type}" "${Comment}"
done
echo '}'

@Hayao0819
Copy link
Author

Hayao0819 commented May 3, 2021

ありがとうございます。かなり勉強になりました...

@colrichie
Copy link

colrichie commented May 17, 2021

#!/bin/sh

Dir=/usr/share/applications                             # アプリ情報のあるDir名を設定

type makrj.sh || {
	cat <<-MSG
		このシェルスクリプトは、makrj.shというコマンドを必要とします。
		下記の場所からダウンロードして、実行ビットを立てて、
		PATHの通っている場所に置いてから本コマンドをもう一度実行してください。
		https://raw.githubusercontent.com/ShellShoccar-jpn/Parsrs/master/makrj.sh
		なお、makrj.shが何者か知りたい場合はこちらをどうぞ。
		https://qiita.com/richmikan@github/items/0dc3330163c86b249bcd
	MSG
	exit 1
}

awk '{s=ARGV[ARGIND];                                   #   1)ファイルパスをsに代入
      sub(/^.*[/]/,"",s);                               #   2)sをファイル名のみに
      sub(/\.[^.]*$/,"",s);                             #   3)sから".desktop"をトル
      gsub(/\./,"_",s);                                 #   4)全ての"."を"_"に置換
      printf("$.%s. %s\n",s,$0);}' ${Dir}/*             | # 5)「JSONPathと値」の前駆体を出力
# 現時点の列構成 1:"$.(アプリ名)" 2:"(属性名)=(値)"     #
grep -E '^[^[:blank:]]+ (Name|Comment|Exec|Icon|Type)=' | # 6)必要な属性名だけに絞る
sed 's/ \([^=]*\)=/\1 /'                                | # 7)「JSONPathと値」にする
# 現時点の列構成 1:"$.(アプリ名).(属性名)" 2:"(値)"     #
makrj.sh                                                #   8)「JSONPathと値」からJSONに変換

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment