Import updates to some utilities that were sitting in my personal bin
[utils/utils.git] / lesspipe.sh
1 #!/bin/bash
2 # Matt's improved version of Fedora's lesspipe.sh
3 #
4 # To use this filter with less, define LESSOPEN:
5 # export LESSOPEN="|lesspipe.sh %s"
6
7 # Updated for "less" that checks our exit code ~ Matt 2010-12-28
8
9 # This script is entirely driven by the file content, with type detected by
10 # file(1).  For reproducibility and to avoid problems with strange names and
11 # even symlinks, we do not use the original name of the file for anything.
12
13 set -e
14 set -o pipefail
15 #set -x
16
17 # Attach the file to an FD.  We will reopen /dev/fd/$f to get as many fresh FDs as
18 # we need.
19 exec {f}<"$1"
20 shift
21
22 function run_stream_stdin {
23         if [ "$decompressor" == cat ]; then
24                 <&$f "$@"
25         else
26                 <&$f "$decompressor" | "$@"
27         fi
28 }
29
30 function stage_to_file_stdin {
31         if [ "$decompressor" != cat ]; then
32                 # There is an unavoidable race here, so I am not trying to avoid
33                 # it.  If we rewrite the script in Perl, we can use open(undef).
34                 # Integration with in2tempfile looks too hard.
35                 tmpfile="$(mktemp --tmpdir lesspipe.XXXXXXXXXX)"
36                 exec {tf}>"$tmpfile"
37                 rm -f "$tmpfile"
38                 <&$f "$decompressor" >&$tf # Synchronous
39                 # Get a new FD for reading on the tempfile.  This might not be
40                 # necessary if the command is just going to reopen it.
41                 exec {f}>&- </dev/fd/$tf {tf}>&-
42         else
43                 exec <&$f-
44         fi
45 }
46
47 # Note: the bash file tests (except -h) follow symlinks.
48 if [ -f /dev/fd/$f ]; then
49         # Specify the input as -.  This avoids irrelevant "set[ug]id" output for
50         # executables and also follows the symlink without us having to pass -L.
51         # We have to reopen stdin to not consume it.
52         content_type="$(</dev/fd/$f file -b -)"
53
54         case "$content_type" in
55         ('gzip compressed data'*)
56                 decompressor=gunzip;;
57         ('bzip2 compressed data'*)
58                 decompressor=bunzip2;;
59         ('XZ compressed data'*)
60                 decompressor=unxz;;
61         (*)
62                 decompressor=cat;;
63         esac
64
65         if [ "$decompressor" != cat ]; then
66                 # Get the real content type.  Whatever work the decompressor
67                 # does before it is killed with SIGPIPE, we will duplicate later
68                 # when we read the file, but there is no good alternative:
69                 # - If we do a "tee" with one pipe held for later use, "tee"
70                 #   will block once it fills up that pipe.  So if "file" needs
71                 #   more data than the pipe buffer to make its decision
72                 #   (probably unlikely), we could hang. 
73                 # - Going ahead and dumping the decompressed data to a temporary
74                 #   file is a waste of work if the content type turns out to be
75                 #   one for which we have a streaming viewer, but of course we
76                 #   don't know that yet.
77                 content_type="$(</dev/fd/$f "$decompressor" | file -b -)" || [ $? == 141 ] # SIGPIPE
78         fi
79
80         case "$content_type" in
81         ('troff or preprocessor input'*)
82                 ## Presuming our parent is "less", set COLUMNS based on the width
83                 ## of the terminal.
84                 #exec {pt}>/proc/$PPID/fd/1
85                 #if [ -t $pt ]; then
86                 #       # XXX Does this really use stderr rather than the controlling terminal?
87                 #       export COLUMNS="$(bash -i -c 'echo $COLUMNS' 2>&$pt)"
88                 #fi
89                 #exec {pt}>&-
90                 # viapty is a more comprehensive solution than the above. ~ 2011-09-05
91                 # Hm, by the time we hack in PAGER=cat, maybe it's not any better... ~ 2011-09-06
92                 run_stream_stdin viapty man -l -;;
93         (*'tar archive'*)
94                 run_stream_stdin tar -tvv;;
95         (*'cpio archive'*)
96                 run_stream_stdin cpio --quiet -itv;;
97         (RPM*)
98                 run_stream_stdin rpm -qivl --changelog -p -;;
99         (*image*) # FIXME: will catch disk "images" as well as graphical images
100                 if type identify &>/dev/null; then
101                         identify_cmd=(identify)
102                 elif type gm &>/dev/null; then
103                         identify_cmd=(gm identify)
104                 else
105                         ### Give them the binary data.
106                         #echo "No identify available"
107                         #echo "Install ImageMagick or GraphicsMagick to browse images"
108                         identify_cmd=(false)
109                 fi
110                 if [ "${identify_cmd[0]}" != false ]; then
111                         # Make a file so identify won't stage it again with a
112                         # nonreproducible name.  (Note that "identify -" will
113                         # stage again even if /dev/stdin is a file!)
114                         stage_to_file_stdin
115                         "${identify_cmd[@]}" /dev/stdin
116                 fi;;
117         ('OpenDocument Text'|_,'OpenDocument Text Template')
118                 stage_to_file_stdin
119                 ### CUSTOM SCRIPT -- This part cannot be upstreamed unless odt2text is.
120                 odt2text /dev/stdin # based on unzip, needs a file
121                 ;;
122         # This has to come after the special case for odt2text.
123         ('Zip archive data'*|_,'OpenDocument'*|_,'OpenOffice 1.x'*)
124                 stage_to_file_stdin
125                 zipinfo /dev/stdin # needs a file
126                 ;;
127         ('PDF document'*)
128                 run_stream_stdin pdftotext - -;;
129         ('PostScript document'*)
130                 # If you wanted to see the PostScript source code, sorry...
131                 run_stream_stdin ps2ascii;;
132         (ELF*'dynamically linked'*)
133                 stage_to_file_stdin # read twice
134                 objdump -xRTdgl /dev/stdin
135                 # The above does not seem to show data sections.  Run a separate command
136                 # to show them.  (Of course I would prefer a single command.)
137                 objdump -s -j .rodata -j .data /dev/stdin
138                 ;;
139         (ELF*)
140                 stage_to_file_stdin # read twice
141                 objdump -xdgl /dev/stdin
142                 objdump -s -j .rodata -j .data /dev/stdin
143                 ;;
144         ('SQLite 3.x database'*)
145                 # TODO: Reimplement copy to a temporary directory to
146                 # handle databases that use write-ahead mode, etc.  I had
147                 # implemented this in main VM and I forgot to copy it to
148                 # main-template before shutting down. :( ~ Noted 2015-12-28
149                 stage_to_file_stdin
150                 # I believe that this will honor a write lock held on an
151                 # uncompressed database by another instance of SQLite.  I
152                 # further believe that is what we want.
153                 sqlite3 /dev/stdin .dump # needs a file
154                 ;;
155         ('compiled Java class data'*)
156                 # Agghhh... why couldn't javap just take a filename?  It's clear
157                 # that all it is doing is translating our argument to a file and
158                 # opening that, without checking the correctness of the package
159                 # or class name.
160                 # XXX: Proper cleanup on failure
161                 dir="$(mktemp -d --tmpdir lesspipe-javap.XXXXXXXXXX)"
162                 ln -s /dev/stdin "$dir/Stdin.class"
163                 run_stream_stdin javap -classpath "$dir" -private -s -c -verbose Stdin
164                 rm -rf "$dir"
165                 ;;
166         # Disabled for now.
167         #(*.grl)
168         #       stage_to_file_stdin
169         #       head -n 1 /dev/stdin | fold -w 28
170         #       tail -n +2;;
171         (*) # Unknown content type.
172                 if [ "$decompressor" != cat ]; then
173                         # Just decompress the file.
174                         <&$f "$decompressor"
175                 else
176                         # We cannot add value in this case.
177                         # Let "less" show the original file.
178                         exit 77
179                 fi;;
180         esac
181
182 elif [ -d /dev/fd/$f ]; then
183         # nip blank line at the end
184         # XXX: how to make sure "less" has -R enabled?
185         #ls -H -al --color=always -- /dev/fd/$f/ | head --bytes=-3
186         ### Between coreutils-7.6-9.matt1.fc12 and coreutils-8.5-7.fc14, the
187         ### output has changed to put the \n at the end, so the special hack is
188         ### no longer necessary (less ignores a final \n) and in fact corrupts
189         ### the colors. ~ Matt 2011-06-19
190         ls -H -al --color=always -- /dev/fd/$f/
191 else
192         # Device/special file.  Don't print anything; let less complain about it.
193         # (For now, we are not interested in content sniffing on pipes.  If less
194         # gained support for calling LESSOPEN for stdin, we might reconsider.)
195         exit 77
196 fi