Created
January 17, 2017 02:47
-
-
Save pkoppstein/b9d4085703944114e72fbe4afcd7d0d1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Copyright (c) 2017 Peter Koppstein (pkoppstein at gmail dot com) 2017.01.15 | |
# | |
# 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. | |
# | |
# Credits: http://opensource.org/licenses/MIT (The MIT License (MIT) | |
VERSION=0.3 | |
function help { | |
cat<<EOF | |
Syntax: $0 [OPTIONS] PATHNAME ... | |
This script provides a read-eval-print loop for jq. | |
Unless an input line specifies a special action, it triggers an | |
invocation of jq as follows: | |
jq OPTS 'COMMANDLINE' [PN ...] | |
where: | |
OPTS denotes the jq options currently in effect, these being | |
initially the options as specified by OPTIONS, if any; | |
COMMANDLINE is the line that has been read from STDIN; | |
PN ... is a list of pathnames, initially the list of PATHNAMEs. | |
If the first PATHNAME is - then the data on STDIN will be read as if | |
it is a file. | |
If the entered line begins with a pipe (|), then the output produced | |
by the most recent invocation of jq is provided as input to | |
the invocation: | |
jq OPTS 'COMMANDLINE' | |
where OPTS is as above, and COMMANDLINE is the entered line stripped | |
of the initial pipe character. | |
If an entered line ends with a pipe (|), then it is concatenated | |
with the next line that does not specify a special action as | |
detailed below. | |
If an entered line is blank, this script exits. | |
If a line begins with one of the following commands, then | |
the indicated action is immediately taken: | |
:exit - exit this script | |
:help - same as ? | |
:input PATHNAME ... - reset PATHNAME ... | |
:options OPTIONS - reset the command-line options to OPTIONS | |
:save PN - save the most recent output into the specified file provided | |
it does not exist | |
:save! PN - save the most recent output into the specified file | |
:save - save to the file most recently created by a :save or :save! command | |
:show - print the OPTIONS, PATHNAMEs and SAVE pathname currently in effect | |
:! PN - equivalent to the sequence of commands | |
:save! PN | |
:input PN | |
? - print brief help including options currently in effect | |
# - the line is ignored | |
Options: | |
OPTIONS may be any combination of jq options, but if -h or --help is | |
specified, then only the help text for this script is printed. | |
Warning: if the -s option is in effect, it will be applied blindly. | |
ANNOTATED EXAMPLE | |
$0 -n -c | |
Enter a jq filter (possibly beginning and/or ending with "|"), or blank line to terminate: | |
[2,3] | map(.+1) | |
[3,4] # response | |
:! temp | |
saved | |
. | |
null # the -n command-line option is still in effect | |
:options -c | |
. | |
[3,4] | |
Bye. | |
EOF | |
} | |
VERBOSE= | |
OPTIONS= | |
while [ "$1" ] | |
do case "$1" in | |
-h | --help ) help | |
exit | |
;; | |
-v | --verbose ) VERBOSE=1 | |
shift | |
;; | |
-L ) OPTIONS="$OPTIONS $1 $2" | |
shift 2 | |
;; | |
--arg | --argjson | --slurpfile ) OPTIONS="$OPTIONS $1 $2 $3" | |
shift 3 | |
;; | |
- ) break | |
;; | |
-* ) OPTIONS="$OPTIONS $1" | |
shift | |
;; | |
* ) break | |
;; | |
esac | |
done | |
function cleanup { | |
test -r "$TMP" && /bin/rm $TMP | |
test -r "$TMP.out" && /bin/rm $TMP.out | |
test -r "$TMP.stdin" && /bin/rm $TMP.stdin | |
} | |
trap cleanup EXIT | |
TMP=$(mktemp /tmp/jqplay.XXX) | |
FILES=("$@") | |
if [ "$1" = - ] ; then | |
cat > $TMP.stdin | |
shift | |
FILES[0]=$TMP.stdin | |
# Ensure we can use "read" | |
exec 0< /dev/tty | |
fi | |
# Global: CMD FILES OPTIONS TMP SAVEPATHNAME | |
function multiline { | |
CMD="" | |
local line | |
local file | |
while [[ $CMD == "" || $CMD =~ \|\ *$ ]] ; do | |
read -r line | |
case "$line" in | |
"#"* ) | |
echo "$line" | |
continue | |
;; | |
":!"* ) | |
file=(${line/:\!/}) | |
if [ "$file" = "" ] ; then | |
echo "$0:" 'a pathname for :! has not been specified' >&2 | |
else | |
cp -ip "$TMP" "$file" | |
if [ $? = 0 ] ; then | |
echo saved | |
SAVEPATHNAME="$file" | |
FILES=("$file") | |
else | |
echo "NOT saved to $file" | |
fi | |
fi | |
continue | |
;; | |
":save!"* ) | |
file=(${line/:save\!/}) | |
if [ "$file" = "" ] ; then | |
if [ -z "$SAVEPATHNAME" ] ; then | |
echo "$0:" 'a pathname for :save! has not been specified' >&2 | |
else | |
cp -p "$TMP" "$SAVEPATHNAME" | |
if [ $? = 0 ] ; then echo saved ; fi | |
fi | |
else | |
cp -ip "$TMP" "$file" | |
if [ $? = 0 ] ; then echo saved ; fi | |
SAVEPATHNAME="$file" | |
fi | |
continue | |
;; | |
:save* ) | |
file=(${line/:save/}) | |
if [ "$file" = "" ] ; then | |
if [ -z "$SAVEPATHNAME" ] ; then | |
echo "$0: a pathname for :save has not been specified" >&2 | |
else | |
cp -p "$TMP" "$SAVEPATHNAME" | |
if [ $? = 0 ] ; then echo saved ; fi | |
fi | |
elif [ -s "$file" ] ; then | |
echo "$0: file $file already exists" >&2 | |
else | |
cp -ip "$TMP" "$file" | |
if [ $? = 0 ] ; then echo saved ; fi | |
SAVEPATHNAME="$file" | |
fi | |
continue | |
;; | |
:show* ) | |
echo OPTIONS=$OPTIONS | |
if [ -n "$SAVEPATHNAME" ] ; then | |
echo ":save $SAVEPATHNAME" | |
fi | |
for line in ${FILES[@]} ; do echo "$line" ; done | |
continue | |
;; | |
:input* ) | |
FILES=(${line/:input/}) | |
if [ "${FILES[0]}" = "-" -a -r $TMP.stdin ] ; then | |
FILES[0]=$TMP.stdin | |
fi | |
continue | |
;; | |
:options* ) | |
OPTIONS=${line/:options/} | |
continue | |
;; | |
"" | :exit* ) | |
if [ "$CMD" != "" ] ; then | |
echo "$0 - discarding $CMD" | |
fi | |
CMD=:exit | |
return | |
;; | |
:help* | "?"* ) | |
cat <<EOF | |
An initial | signifies the filter should be applied to the previous jq | |
output. | |
A terminating | causes the next line that does not trigger a special | |
action to be appended to the current line. | |
Special action triggers: | |
:exit # exit this script, also triggered by a blank line | |
:help # print this help | |
:input PATHNAME ... | |
:options OPTIONS | |
:save PN # save the most recent output in the named file provided | |
it does not exist | |
:save! PN # save the most recent output in the named file | |
:save # save to the file most recently specified by a :save command | |
:show # print the OPTIONS and PATHNAMEs currently in effect | |
:! PN # equivalent to the sequence of commands | |
:save! PN | |
:input PN | |
? # print this help | |
# # ignore this line | |
EOF | |
;; | |
*'|' ) | |
CMD="$CMD $line" | |
### echo multiline has set CMD to: "$CMD" > /dev/stderr | |
;; | |
* ) CMD="$CMD $line" | |
break | |
;; | |
esac | |
done | |
CMD=$(sed -e 's/^ *//' <<< "$CMD") | |
### echo multiline returning "$CMD" > /dev/stderr | |
} | |
echo 'Enter a jq filter (possibly beginning and/or ending with "|"), or blank line to terminate:' | |
while true | |
do | |
multiline | |
case "$CMD" in | |
":exit" ) echo Bye. ; exit | |
;; | |
'|'* ) | |
CMD="${CMD/|/}" | |
### echo jq $OPTIONS "$CMD" from $TMP | |
### cat $TMP | |
jq $OPTIONS "$CMD" < $TMP > $TMP.out | |
/bin/mv $TMP.out $TMP | |
;; | |
* ) | |
jq $OPTIONS "$CMD" "${FILES[@]}" > $TMP | |
;; | |
esac | |
cat $TMP | |
done | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment