Rename setexec to chexec.
[utils/utils.git] / lesspipe.sh
CommitLineData
273c3903
MM
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
13set -e
14set -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.
19exec {f}<"$1"
20shift
21
22function run_stream_stdin {
23 if [ "$decompressor" == cat ]; then
24 <&$f "$@"
25 else
26 <&$f "$decompressor" | "$@"
27 fi
28}
29
30function 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.
48if [ -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
182elif [ -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/
191else
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
196fi