Skip to content

Instantly share code, notes, and snippets.

@pkoppstein
Created January 17, 2017 02:47
Show Gist options
  • Save pkoppstein/b9d4085703944114e72fbe4afcd7d0d1 to your computer and use it in GitHub Desktop.
Save pkoppstein/b9d4085703944114e72fbe4afcd7d0d1 to your computer and use it in GitHub Desktop.
#!/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