Repository navigation
Expand file tree
/
Copy pathplx.min.sh
More file actions
13 lines (10 loc) · 9.72 KB
/
Copy pathplx.min.sh
File metadata and controls
13 lines (10 loc) · 9.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/zsh
#!/bin/bash
#!/bin/sh
#if you embed plx in your own script it works in these shells
: <<-LICENSE_BLOCK
plx - "plist extractor" (minified) (https://github.com/brunerd/plx) - Copyright (c) 2025 Joel Bruner
Licensed under the MIT License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LICENSE_BLOCK
function plx()({ set +x; } &> /dev/null;[ -n "${ZSH_NAME}" ] && set -y;helpText=$'plx 1.0 - plist extractor';function getPlistPropertyType(){ [ -t '0' ] && return 1;cat | /usr/bin/xmllint --xpath "local-name(/plist/*)" 2>/dev/null -; };function plistQuery(){ [ -t '0' ] && return 1;local queryArg=$(sed $'s/\'/\\\\\'/g' <<< "${1}");local plistData; read -r -d '' plistData <<< "$(cat)";if ! xmllint - 2>/dev/null 1>&2 <<< "${plistData}"; then return 1;fi;if [ -z "${queryArg}" ] || [ "${queryArg}" = ':' ]; then /usr/bin/xmllint --format - 2>/dev/null <<< "${plistData}";return 0;fi;local queryArgRS=$(sed $'s/\\\\:/\x1e/g' <<< "${queryArg}");local parentPath=$(sed $'s/\x1e/\\\\:/g' <<< "${queryArgRS%:*}"); [ "${queryArg}" = "${parentPath}" ] && unset parentPath;local finalElement="${queryArgRS##*:}";if [ -n "${finalElement}" ] && ! grep -q -E '^\d+$' <<< "${finalElement}"; then local parentXMLData; read -r -d '' parentXMLData <<< "$(/usr/libexec/PlistBuddy -x -c "print '${parentPath}'" /dev/stdin 2>/dev/null <<< "${plistData}")";local parentPathDataType=$(getPlistPropertyType <<< "${parentXMLData}");if [ "${parentPathDataType}" = "array" ]; then echo "error: cannot resolve non-numeric key name for array: ${parentPath}" >&2;return 1;fi;if [ "${finalElement}" = 'CF$UID' ] && [ "$(grep -c '<key>' <<< "${parentXMLData}")" = "1" ]; then grep -q '<integer>' 2>/dev/null <<< "${parentXMLData}" && local CFUIDFIX=1;fi;fi;if ((CFUIDFIX)); then /usr/libexec/PlistBuddy -x -c "print '${queryArg}'" /dev/stdin 2>/dev/null <<< "$(sed 's/integer>/string>/g' <<< "${plistData}")" | sed 's/string>/integer>/g' | /usr/bin/xmllint --format - 2>/dev/null;else /usr/libexec/PlistBuddy -x -c "print '${queryArg}'" /dev/stdin 2>/dev/null <<< "${plistData}" | /usr/bin/xmllint --format - 2>/dev/null;fi; };function listKeysOrLength(){ [ -t '0' ] && return 1;local plistData; read -r -d '' plistData <<< "$(cat)";local dataType=$(/usr/bin/xmllint --xpath "local-name(/plist/*)" 2>/dev/null - <<< "${plistData}");case "${dataType}" in "dict")/usr/bin/xmllint --xpath "/plist/dict/key/text()" - 2>/dev/null <<< "${plistData}" | /usr/bin/perl -MHTML::Entities -pe 'decode_entities($_)' | sed 's/:/\\:/g';;"array")/usr/bin/xmllint --xpath "count(/plist/array/*)" - 2>/dev/null <<< "${plistData}";;*)echo "error: list/length requires dict or array" >&2;return 1;;esac;return 0; };function outputPlistAsText(){ [ -t '0' ] && return 1;local plistData; read -r -d '' plistData <<< "$(cat)";local plistDataType=$(getPlistPropertyType <<< "${plistData}");case "${plistDataType}" in "date"|"integer"|"real")/usr/bin/xmllint --xpath "/plist/${plistDataType}/text()" - 2>/dev/null <<< "${plistData}";;true) echo "true";;false) echo "false";;"string")local printData=$(/usr/libexec/PlistBuddy -c "print" /dev/stdin 2>/dev/null <<< "${plistData}");if [ "${1}" = "r" ]; then cat <<< "${printData}";else local printDataType=$(file -b - <<< "${printData}");case "${printDataType}" in "XML 1.0 document text"*)if ! /usr/bin/xmllint --format - 2>/dev/null <<< "${printData}"; then cat <<< "${printData}";fi;;"JSON data")/usr/bin/plutil -convert json -r - -o - <<< "${printData}"; echo;;*)cat <<< "${printData}";esac;fi;;"array")if ! grep -q -E "<dict>|<data>" <<< "${plistData}"; then sed 's|<true/>|<string>true</string>|g; s|<false/>|<string>false</string>|g' <<< "${plistData}" | /usr/bin/xmllint --xpath '/plist/array//*[self::string or self::integer or self::real or self::date]/text()' - 2>/dev/null | /usr/bin/perl -MHTML::Entities -pe 'decode_entities($_)' 2>/dev/null;else /usr/bin/xmllint --format - 2>/dev/null <<< "${plistData}";fi;;"dict")if (($(listKeysOrLength <<< "${plistData}" | wc -l))); then /usr/bin/xmllint --format - 2>/dev/null <<< "${plistData}";fi;;esac;return 0; };function findDomainFilePath(){ local domainArg="${1}";local domainNameOnly="$(basename "${domainArg%.plist}")";case "${domainArg}" in "Apple Global Domain"|"NSGlobalDomain") domainNameOnly=".GlobalPreferences";;esac;local homeFolder=$(/usr/libexec/PlistBuddy -c "print dsAttrTypeStandard\:NFSHomeDirectory:0" /dev/stdin 2>/dev/null <<< "$(dscl -plist . -read "/Users/$(whoami)" NFSHomeDirectory)");find "${homeFolder}/Library/Preferences" "${homeFolder}/Library/Containers/${domainNameOnly}" "${homeFolder}/Library/Containers" -type f -path '*/Library/Preferences/*' -name "${domainNameOnly}.plist" -print -quit 2>/dev/null; };function ISO2EPOCH(){ [ -t 0 ] && return 1; date -j -u -f '%Y-%m-%dT%H:%M:%SZ' "$(cat)" +%s; };[ "$(sw_vers -productVersion | cut -d. -f1)" -lt 13 ] && { echo "error: macOS 13 or higher required" >&2; return 1; };[ -n "${BASH_VERSION}" ] && local OPTIND OPTARG;while getopts ":ehlftxz" option; do case "${option}" in 'e'|'l'|'t'|'x')[ -z "${myOp}" ] && myOp="${option}";;'h')[ -t '0' ] && echo "${helpText}" | less; exit;;'z')set -x;;esac;done;[ $OPTIND -ge 2 ] && shift $((OPTIND-1));if ! [ -t '0' ]; then input_b64=$(cat|base64);input_filetype=$(base64 -D <<< "${input_b64}" | file -b -);case "${input_filetype}" in "Apple binary property list"|"XML 1.0 document text"*)[ "${myOp}" = "p" ] && { echo "/dev/stdin"; return 0; };read -r -d '' plistData <<< "$(base64 -D <<< "${input_b64}" | plutil -convert xml1 -o - - 2>/dev/null)";;*)echo "error: input is not property list data." >&2;return 1;;esac;else fileArg="${1}";if [ -z "${fileArg}" ]; then echo "error: specify domain name or provide plist data via file argument, redirection or pipe; -h for help." >&2;return 1;elif [ -f "${fileArg}" ]; then if ! [ -r "${fileArg}" ]; then echo "error: file cannot be read, check permissions." >&2;return 1;fi;input_filetype=$(file -b "${fileArg}" 2>/dev/null);case "${input_filetype}" in "Apple binary property list"|"XML 1.0 document text"*)[ "${myOp}" = "f" ] && { echo "${fileArg}"; return 0; };read -r -d '' plistData <<< "$(plutil -convert xml1 "${fileArg}" -o - 2>/dev/null)";;*)echo "error: file is not a valid property list." >&2;return 1;;esac;else if defaults read "${fileArg}" 2>/dev/null 1>&2; then read -r -d '' plistData <<< "$(defaults export "${fileArg}" -)";else domainPath=$(findDomainFilePath "${fileArg}");if ! [ -f "${domainPath}" ] || ! plistData=$(plutil -convert xml1 "${domainPath}" -o - 2>/dev/null); then echo "error: file/domain \"${fileArg}\" not found/valid" >&2;return 1;fi;fi;fi;shift 1;fi;if ! plutil -lint - 2>/dev/null 1>&2 <<< "${plistData}"; then echo "error: invalid property list data." >&2;return 1;fi;queryArg="${1:-:}";grep -q '|$' <<< "${queryArg}" && queryArg="${queryArg}:";queryArgEscaped=$(sed $'s/\\\\|/\x1e/g' <<< "${queryArg}");IFS=$'|';for queryArgElement in ${queryArgEscaped}; do queryArgElement=$(sed $'s/\x1e/|/g' <<< "${queryArgElement}");if ((queryLooped)); then read -r -d '' data_b64 <<< "$(/usr/bin/xmllint --xpath "/plist/data/text()" - 2>/dev/null <<< "${queryData}")";dataFileType=$(base64 -D <<< "${data_b64}" | file -b -);case "${dataFileType}" in 'Apple binary property list'|'XML 1.0 document text'*)if ! plistData="$(base64 -D <<< "${data_b64}" | /usr/bin/plutil -convert xml1 - -o - 2>/dev/null)"; then echo "error: pipe used on non-plist data node" >&2;return 1;fi;;*)echo "error: pipe used on non-plist data node" >&2;return 1;;esac;fi;if ! queryData=$(plistQuery "${queryArgElement}" <<< "${plistData}"); then if [ -n "${queryArgElement}" ]; then echo "error: key/path not found: ${queryArgElement}" >&2;fi;return 1;else read -r -d '' plistData <<< "${queryData}";fi;let queryLooped++;done;queryDataType=$(getPlistPropertyType <<< "${queryData}");case "${myOp}" in 'e')case "${queryDataType}" in "date") outputPlistAsText <<< "${queryData}" | ISO2EPOCH;;*) echo "error: option -${myOp} for date type key only"; return 1;;esac;return 0;;'l')listKeysOrLength <<< "${queryData}" && return 0 || return 1;;'t')case "${queryDataType}" in true|false) echo "bool";; *) echo "${queryDataType}";; esac;return 0;;'x')/usr/bin/xmllint --format - 2>/dev/null <<< "${queryData}";return 0;;esac;if [ "${queryDataType}" = "data" ]; then data_b64=$(/usr/bin/xmllint --xpath "/plist/data/text()" - 2>/dev/null <<< "${queryData}");dataFileType=$(base64 -D <<< "${data_b64}" | file -b -);case "${dataFileType}" in "Apple binary property list"|"XML 1.0 document text"*)if ! output=$(base64 -D <<< "${data_b64}" | /usr/bin/plutil -convert xml1 - -o - 2>/dev/null | xmllint --format - 2>/dev/null); then base64 -D <<< "${data_b64}" | xmllint --format - 2>/dev/null;return 0;else outputPlistAsText "${myOp}" <<< "${output}";return 0;fi;;"ASCII text, with no line terminators")base64 -D <<< "${data_b64}"; echo;return 0;;*)base64 -D <<< "${data_b64}";return 0;;esac;else outputPlistAsText "${myOp}" <<< "${queryData}";return 0;fi)
plx "$@"