premise

This perl script enables you to do code generation and intelligent ‘quoting’ and ‘unquoting’.

examples

While coding, it might be necessary to take a chunk of code and turn it into a string

input

1
2
3
4
5
getopts("d:Jufnbthl", \%options);

if (defined $options{d}) {
    $delim = $options{d};
}

pipe the input into the q script

1
cat | q
"getopts(\"d:Jufnbthl\", \\%options);\n\nif (defined $options{d}) {\n    $delim = $options{d};\n}"

cmd script for bash code generation

Basic version, without the q script.

1
2
3
4
5
6
7
8
9
#!/bin/sh
export TTY

# This also handles unicode correctly

for var in "$@"
do
    printf "'%s' " "$(printf %s "$var" | sed "s/'/'\\\\''/g")";
done | sed 's/ $//'

nice version, with the q script (handles double quotes).

The cmd script relies on the q script.

What it does is output the arguments you supply to it, intelligently. This allows you to create macros in bash.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/bin/bash
export TTY

while [ $# -gt 0 ]; do opt="$1"; case "$opt" in
    -f) {
        CMD_EXPRESSION="$2"
        shift
        shift
    }
    ;;

    -E) {
        CMD="$2"
        shift
        shift
    }
    ;;

    -d) {
        CMD_DELIM="$2"
        shift
        shift
    }
    ;;

    -) {
        USE_STDIN=y
        shift
    }
    ;;

    --) {
        shift
        break
    }
    ;;

    *) break;
esac; done

: ${CMD_EXPRESSION:="q"}
: ${CMD_DELIM:=" "}

if test -n "$CMD"; then
    export CMD_EXPRESSION
    export CMD_DELIM
    CMD="$(printf -- "%s" "$CMD" | bs '$')"
    eval "cmd $CMD"
    exit $?
fi

stdin_exists() {
    ! [ -t 0 ]
}

if test "$#" -eq 0 && ! { test "$USE_STDIN" = y && stdin_exists; }; then
    exit 0
fi

if test "$#" -eq 0 && stdin_exists; then
    awk 1 | while IFS=$'\n' read -r line; do
        if test -n "$line"; then
            export CMD_EXPRESSION
            export CMD_DELIM
            odn cmd -E "$line"
        fi
    done
fi

printf -- "%s" "$(
for (( i = 1; i < $#; i++ )); do
    eval ARG=\${$i}
    if test -z "$ARG"; then
        printf '""'
    else
        printf -- "%s" "$ARG" | eval "$CMD_EXPRESSION"
    fi
    echo -n -e "$CMD_DELIM"
done
eval ARG=\${$i}
if test -z "$ARG"; then
    # This test is for the specific case of { cmd yo yo yo | cmd - -d "\n"; }
    test "$#" -gt 0 && printf '""'
else
    printf -- "%s" "$ARG" | eval "$CMD_EXPRESSION"
fi
)"

examples of using cmd (basic version)

1
cmd -f one two "three, four and five" "With some 'single' quotes"
'cmd' 'cmd' '-f' 'one' 'two' 'three, four and five' 'With some '\''single'\'' quotes'

As you can see, single quotes are used. This is really nice, except sometimes you need to use double quotes. That’s where the perl version comes in. That’s actually tricky to do, but this is actually really awesome.

1
cmd-nice -- -f one two "three, four and five" "With some \"double\" quotes"
-f one two "three, four and five" "With some \"double\" quotes"

Here are some of the other options you can supply to q

-l operate on each line as a separate string instead of the whole of stdin.

1
2
3
4
5
6
7
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

1
q -l

"# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES"
"# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF"
"# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR"
"# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES"
"# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN"
"# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF"
"# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE."

Then unquote them all

1
uq -l
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

q script (perl)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!/usr/bin/perl -X
# q: string Quoting utility
# Copyright (C) 2017 Shane Mulligan <mullikine@gmail.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# -X prevents it from printing errors

# Pretty good elisp version. Doesn't work past lispy.
#vim +/"(defun lispy-stringify (&optional arg)" "$HOME$MYGIT/spacemacs/packages26/lispy-20180317.722/lispy.el"
#vim +/"(defun lispy-unstringify ()" "$HOME$MYGIT/spacemacs/packages26/lispy-20180317.722/lispy.el"

# http://search.cpan.org/~evo/String-Escape-2010.002/Escape.pm#INSTALLATION

# Examples:
# cat $HOME/scripts/s | s awrl "q -c | q -cu"

# use IO::Handle;
# STDOUT->autoflush;

# Unbuffers input. But only is effective from tty
# use Term::ReadKey; ReadMode 3;

# Unbuffers output.
$|++;

# I want this script to not be buffered

use File::Basename;
my $name = basename($0);


# use strict;
use Getopt::Std;

# declare the perl command line flags/options we want to allow
my %options=();

# echo "hi\nhi hi" | qf
# q full/force. Puts quotes on. same as q -ftln
if ($name eq "qftln") {
    $options{f} = 1;
    $options{t} = 1;
    $options{l} = 1;
    $options{n} = 1;
}

# turn lines into arguments. Great for ranger.
if ($name eq "qf" or $name eq "qargs" or $name eq "qfdl") {
    $options{f} = 1;
    $options{d} = " ";
    $options{l} = 1;
}

if ($name eq "Q") {
    $options{f} = 1;
    $options{l} = 1;
}

# echo "hi\nhi hi" | qs
# q simple. Only adds quotes when necessary. same as q -tln
if ($name eq "qs") {
    $options{t} = 1;
    $options{l} = 1;
    $options{n} = 1;
}

if ($name eq "qne") {
    $options{b} = 1;
}

if ($name eq "uq") {
    $options{u} = 1;
}

if ($name eq "unquote.pl") {
    $options{u} = 1;
}

$delim = "\n";

getopts("d:Jufnbthl", \%options);

if (defined $options{d}) {
    $delim = $options{d};
}

if (defined $options{J}) {
    $delim = '';
}

if (defined $options{h}) {
    my $message = <<'END_MESSAGE';
-n no trailing newline (bad for awk coprocess / unbuffering)
-b bare (no ends)
-q unquote
-d output delimeter (default \n)
-l read lines
-J join lines (bad for awk coprocess / unbuffering)
-f force surrounding quotes. Default is to not force if not needed. Good for script wrappers.
-t trim (don't quote leading and trailing whitespace)
END_MESSAGE

    print $message;

    exit 0
}

use String::Escape qw( unquote qqbackslash quote backslash unquotemeta escape printable unprintable unqprintable qprintable );


# TODO :: This script has a deficiency. When I want to force wrapping
# quotes, as sometimes I do want, I want to specify -f
# vim +/"file_path=\"\$(p \"\$file_path\" | qne)\"" "$HOME/scripts/e"


# sudo cpanm String::Escape

# sub say {
    # print "\"",@_, "\""
# }

# c = 0

# This is buffered
# foreach $line (<>)  {

my $leading;
my $middle;
my $trailing;
my $line;

sub quote_substring {
    $subject = $_[0];

    if (defined $options{t}) {
        $leading = $subject;
        $middle = $subject;
        $trailing = $subject;

        $leading =~ s/^(\s*).*/$1/;
        $middle =~ s/^\s*(.*?)\s*$/$1/;
        $trailing =~ s/^\s*.*?(\s*)$/$1/;

        return $leading . quote_it($middle) . $trailing;
    } else {
        return quote_it($subject);
    }
}

sub quote_it {
    $subject = $_[0];

    if (defined $options{u}) {
        # this is bad i don't want the unicode codes to come though
        $subject = unqprintable($subject);
    } else {
        if (defined $options{f}) {
            if (defined $options{b}) {
                $subject = printable($subject);
            } else {
                $subject = quote(printable($subject));
            }
            # print qqbackslash($subject); # this creates those horrid
            # unicode codes
        } else {
            if ($subject ne "") {
                if (defined $options{b}) {
                    $subject = unquote(qprintable($subject));
                } else {
                    $subject = qprintable($subject);
                }
            }
        }
    }

    return $subject;
}

if (! defined $options{l}) {
    local $/;
    my $stdin = <STDIN>;

    print quote_substring($stdin);
    exit 0;
} else {
    while(<>) {
        $chomp_result=0;

        if (! chomp) {
            $chomp_result = 1;
        }

        print quote_substring($_);

        # Annoyingly, a line must always come out after data is
        # received, for awk to not hang.

        if (($chomp_result eq 1) && (defined $options{n})) {
            0;
        } else {
            print $delim;
        }
    }
    # select()->flush();
    exit 0
}

exit 0

These symlinks to the q script start the q script with different options.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Q -> q
qargs -> qf
qfdl -> q
qftln -> qf
qf -> q
qs -> q
uq -> q
qne -> q
unquote.pl -> uq
uqne -> uq