├── .github └── workflows │ └── test.yml ├── .gitignore ├── COPYING ├── ChangeLog ├── INSTALL ├── README.md ├── _less ├── archive_color ├── code2color ├── configure ├── german.txt ├── less_completion ├── lesscomplete ├── lesspipe.1 ├── lesspipe.sh ├── packaging ├── README ├── control └── lesspipe.spec ├── sxw2txt ├── test.pl ├── tests ├── archive.tgz ├── compress.tgz ├── dir.zip ├── filter.tgz └── special.tgz └── vimcolor /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test suite" 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | concurrency: 8 | group: "${{ github.workflow }}-${{ github.ref }}" 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | test-suite: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Run ShellCheck 19 | uses: ludeeus/action-shellcheck@master 20 | 21 | - name: Install dependencies 22 | run: | 23 | sudo apt-get -q update 24 | sudo apt-get install --no-install-recommends -qy \ 25 | brotli \ 26 | cabextract \ 27 | csvkit \ 28 | djvulibre-bin \ 29 | genisoimage \ 30 | ghostscript \ 31 | gnupg \ 32 | groff \ 33 | hdf5-tools \ 34 | id3v2 \ 35 | less \ 36 | libarchive-tools \ 37 | libimage-exiftool-perl \ 38 | libreoffice \ 39 | locales-all \ 40 | lz4 \ 41 | lzip \ 42 | p7zip \ 43 | pandoc \ 44 | poppler-utils \ 45 | python3-pygments \ 46 | rpm \ 47 | source-highlight \ 48 | texlive-binaries \ 49 | unrar \ 50 | unrtf \ 51 | vim 52 | 53 | - name: Run test suite 54 | run: | 55 | export TERM=ansi 56 | eval "$(dircolors -b)" 57 | ./test.pl -e 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Makefile* 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | 3 | GNU GENERAL PUBLIC LICENSE 4 | Version 2, June 1991 5 | 6 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 7 | 675 Mass Ave, Cambridge, MA 02139, USA 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | Preamble 12 | 13 | The licenses for most software are designed to take away your 14 | freedom to share and change it. By contrast, the GNU General Public 15 | License is intended to guarantee your freedom to share and change free 16 | software--to make sure the software is free for all its users. This 17 | General Public License applies to most of the Free Software 18 | Foundation's software and to any other program whose authors commit to 19 | using it. (Some other Free Software Foundation software is covered by 20 | the GNU Library General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not 24 | price. Our General Public Licenses are designed to make sure that you 25 | have the freedom to distribute copies of free software (and charge for 26 | this service if you wish), that you receive source code or can get it 27 | if you want it, that you can change the software or use pieces of it 28 | in new free programs; and that you know you can do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid 31 | anyone to deny you these rights or to ask you to surrender the rights. 32 | These restrictions translate to certain responsibilities for you if you 33 | distribute copies of the software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether 36 | gratis or for a fee, you must give the recipients all the rights that 37 | you have. You must make sure that they, too, receive or can get the 38 | source code. And you must show them these terms so they know their 39 | rights. 40 | 41 | We protect your rights with two steps: (1) copyright the software, and 42 | (2) offer you this license which gives you legal permission to copy, 43 | distribute and/or modify the software. 44 | 45 | Also, for each author's protection and ours, we want to make certain 46 | that everyone understands that there is no warranty for this free 47 | software. If the software is modified by someone else and passed on, we 48 | want its recipients to know that what they have is not the original, so 49 | that any problems introduced by others will not reflect on the original 50 | authors' reputations. 51 | 52 | Finally, any free program is threatened constantly by software 53 | patents. We wish to avoid the danger that redistributors of a free 54 | program will individually obtain patent licenses, in effect making the 55 | program proprietary. To prevent this, we have made it clear that any 56 | patent must be licensed for everyone's free use or not licensed at all. 57 | 58 | The precise terms and conditions for copying, distribution and 59 | modification follow. 60 | 61 | GNU GENERAL PUBLIC LICENSE 62 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 63 | 64 | 0. This License applies to any program or other work which contains 65 | a notice placed by the copyright holder saying it may be distributed 66 | under the terms of this General Public License. The "Program", below, 67 | refers to any such program or work, and a "work based on the Program" 68 | means either the Program or any derivative work under copyright law: 69 | that is to say, a work containing the Program or a portion of it, 70 | either verbatim or with modifications and/or translated into another 71 | language. (Hereinafter, translation is included without limitation in 72 | the term "modification".) Each licensee is addressed as "you". 73 | 74 | Activities other than copying, distribution and modification are not 75 | covered by this License; they are outside its scope. The act of 76 | running the Program is not restricted, and the output from the Program 77 | is covered only if its contents constitute a work based on the 78 | Program (independent of having been made by running the Program). 79 | Whether that is true depends on what the Program does. 80 | 81 | 1. You may copy and distribute verbatim copies of the Program's 82 | source code as you receive it, in any medium, provided that you 83 | conspicuously and appropriately publish on each copy an appropriate 84 | copyright notice and disclaimer of warranty; keep intact all the 85 | notices that refer to this License and to the absence of any warranty; 86 | and give any other recipients of the Program a copy of this License 87 | along with the Program. 88 | 89 | You may charge a fee for the physical act of transferring a copy, and 90 | you may at your option offer warranty protection in exchange for a fee. 91 | 92 | 2. You may modify your copy or copies of the Program or any portion 93 | of it, thus forming a work based on the Program, and copy and 94 | distribute such modifications or work under the terms of Section 1 95 | above, provided that you also meet all of these conditions: 96 | 97 | a) You must cause the modified files to carry prominent notices 98 | stating that you changed the files and the date of any change. 99 | 100 | b) You must cause any work that you distribute or publish, that in 101 | whole or in part contains or is derived from the Program or any 102 | part thereof, to be licensed as a whole at no charge to all third 103 | parties under the terms of this License. 104 | 105 | c) If the modified program normally reads commands interactively 106 | when run, you must cause it, when started running for such 107 | interactive use in the most ordinary way, to print or display an 108 | announcement including an appropriate copyright notice and a 109 | notice that there is no warranty (or else, saying that you provide 110 | a warranty) and that users may redistribute the program under 111 | these conditions, and telling the user how to view a copy of this 112 | License. (Exception: if the Program itself is interactive but 113 | does not normally print such an announcement, your work based on 114 | the Program is not required to print an announcement.) 115 | 116 | These requirements apply to the modified work as a whole. If 117 | identifiable sections of that work are not derived from the Program, 118 | and can be reasonably considered independent and separate works in 119 | themselves, then this License, and its terms, do not apply to those 120 | sections when you distribute them as separate works. But when you 121 | distribute the same sections as part of a whole which is a work based 122 | on the Program, the distribution of the whole must be on the terms of 123 | this License, whose permissions for other licensees extend to the 124 | entire whole, and thus to each and every part regardless of who wrote it. 125 | 126 | Thus, it is not the intent of this section to claim rights or contest 127 | your rights to work written entirely by you; rather, the intent is to 128 | exercise the right to control the distribution of derivative or 129 | collective works based on the Program. 130 | 131 | In addition, mere aggregation of another work not based on the Program 132 | with the Program (or with a work based on the Program) on a volume of 133 | a storage or distribution medium does not bring the other work under 134 | the scope of this License. 135 | 136 | 3. You may copy and distribute the Program (or a work based on it, 137 | under Section 2) in object code or executable form under the terms of 138 | Sections 1 and 2 above provided that you also do one of the following: 139 | 140 | a) Accompany it with the complete corresponding machine-readable 141 | source code, which must be distributed under the terms of Sections 142 | 1 and 2 above on a medium customarily used for software interchange; or, 143 | 144 | b) Accompany it with a written offer, valid for at least three 145 | years, to give any third party, for a charge no more than your 146 | cost of physically performing source distribution, a complete 147 | machine-readable copy of the corresponding source code, to be 148 | distributed under the terms of Sections 1 and 2 above on a medium 149 | customarily used for software interchange; or, 150 | 151 | c) Accompany it with the information you received as to the offer 152 | to distribute corresponding source code. (This alternative is 153 | allowed only for noncommercial distribution and only if you 154 | received the program in object code or executable form with such 155 | an offer, in accord with Subsection b above.) 156 | 157 | The source code for a work means the preferred form of the work for 158 | making modifications to it. For an executable work, complete source 159 | code means all the source code for all modules it contains, plus any 160 | associated interface definition files, plus the scripts used to 161 | control compilation and installation of the executable. However, as a 162 | special exception, the source code distributed need not include 163 | anything that is normally distributed (in either source or binary 164 | form) with the major components (compiler, kernel, and so on) of the 165 | operating system on which the executable runs, unless that component 166 | itself accompanies the executable. 167 | 168 | If distribution of executable or object code is made by offering 169 | access to copy from a designated place, then offering equivalent 170 | access to copy the source code from the same place counts as 171 | distribution of the source code, even though third parties are not 172 | compelled to copy the source along with the object code. 173 | 174 | 4. You may not copy, modify, sublicense, or distribute the Program 175 | except as expressly provided under this License. Any attempt 176 | otherwise to copy, modify, sublicense or distribute the Program is 177 | void, and will automatically terminate your rights under this License. 178 | However, parties who have received copies, or rights, from you under 179 | this License will not have their licenses terminated so long as such 180 | parties remain in full compliance. 181 | 182 | 5. You are not required to accept this License, since you have not 183 | signed it. However, nothing else grants you permission to modify or 184 | distribute the Program or its derivative works. These actions are 185 | prohibited by law if you do not accept this License. Therefore, by 186 | modifying or distributing the Program (or any work based on the 187 | Program), you indicate your acceptance of this License to do so, and 188 | all its terms and conditions for copying, distributing or modifying 189 | the Program or works based on it. 190 | 191 | 6. Each time you redistribute the Program (or any work based on the 192 | Program), the recipient automatically receives a license from the 193 | original licensor to copy, distribute or modify the Program subject to 194 | these terms and conditions. You may not impose any further 195 | restrictions on the recipients' exercise of the rights granted herein. 196 | You are not responsible for enforcing compliance by third parties to 197 | this License. 198 | 199 | 7. If, as a consequence of a court judgment or allegation of patent 200 | infringement or for any other reason (not limited to patent issues), 201 | conditions are imposed on you (whether by court order, agreement or 202 | otherwise) that contradict the conditions of this License, they do not 203 | excuse you from the conditions of this License. If you cannot 204 | distribute so as to satisfy simultaneously your obligations under this 205 | License and any other pertinent obligations, then as a consequence you 206 | may not distribute the Program at all. For example, if a patent 207 | license would not permit royalty-free redistribution of the Program by 208 | all those who receive copies directly or indirectly through you, then 209 | the only way you could satisfy both it and this License would be to 210 | refrain entirely from distribution of the Program. 211 | 212 | If any portion of this section is held invalid or unenforceable under 213 | any particular circumstance, the balance of the section is intended to 214 | apply and the section as a whole is intended to apply in other 215 | circumstances. 216 | 217 | It is not the purpose of this section to induce you to infringe any 218 | patents or other property right claims or to contest validity of any 219 | such claims; this section has the sole purpose of protecting the 220 | integrity of the free software distribution system, which is 221 | implemented by public license practices. Many people have made 222 | generous contributions to the wide range of software distributed 223 | through that system in reliance on consistent application of that 224 | system; it is up to the author/donor to decide if he or she is willing 225 | to distribute software through any other system and a licensee cannot 226 | impose that choice. 227 | 228 | This section is intended to make thoroughly clear what is believed to 229 | be a consequence of the rest of this License. 230 | 231 | 8. If the distribution and/or use of the Program is restricted in 232 | certain countries either by patents or by copyrighted interfaces, the 233 | original copyright holder who places the Program under this License 234 | may add an explicit geographical distribution limitation excluding 235 | those countries, so that distribution is permitted only in or among 236 | countries not thus excluded. In such case, this License incorporates 237 | the limitation as if written in the body of this License. 238 | 239 | 9. The Free Software Foundation may publish revised and/or new versions 240 | of the General Public License from time to time. Such new versions will 241 | be similar in spirit to the present version, but may differ in detail to 242 | address new problems or concerns. 243 | 244 | Each version is given a distinguishing version number. If the Program 245 | specifies a version number of this License which applies to it and "any 246 | later version", you have the option of following the terms and conditions 247 | either of that version or of any later version published by the Free 248 | Software Foundation. If the Program does not specify a version number of 249 | this License, you may choose any version ever published by the Free Software 250 | Foundation. 251 | 252 | 10. If you wish to incorporate parts of the Program into other free 253 | programs whose distribution conditions are different, write to the author 254 | to ask for permission. For software which is copyrighted by the Free 255 | Software Foundation, write to the Free Software Foundation; we sometimes 256 | make exceptions for this. Our decision will be guided by the two goals 257 | of preserving the free status of all derivatives of our free software and 258 | of promoting the sharing and reuse of software generally. 259 | 260 | NO WARRANTY 261 | 262 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 263 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 264 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 265 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 266 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 267 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 268 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 269 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 270 | REPAIR OR CORRECTION. 271 | 272 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 273 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 274 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 275 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 276 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 277 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 278 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 279 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 280 | POSSIBILITY OF SUCH DAMAGES. 281 | 282 | END OF TERMS AND CONDITIONS 283 | 284 | Appendix: How to Apply These Terms to Your New Programs 285 | 286 | If you develop a new program, and you want it to be of the greatest 287 | possible use to the public, the best way to achieve this is to make it 288 | free software which everyone can redistribute and change under these terms. 289 | 290 | To do so, attach the following notices to the program. It is safest 291 | to attach them to the start of each source file to most effectively 292 | convey the exclusion of warranty; and each file should have at least 293 | the "copyright" line and a pointer to where the full notice is found. 294 | 295 | 296 | Copyright (C) 19yy 297 | 298 | This program is free software; you can redistribute it and/or modify 299 | it under the terms of the GNU General Public License as published by 300 | the Free Software Foundation; either version 2 of the License, or 301 | (at your option) any later version. 302 | 303 | This program is distributed in the hope that it will be useful, 304 | but WITHOUT ANY WARRANTY; without even the implied warranty of 305 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 306 | GNU General Public License for more details. 307 | 308 | You should have received a copy of the GNU General Public License 309 | along with this program; if not, write to the Free Software 310 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 311 | 312 | Also add information on how to contact you by electronic and paper mail. 313 | 314 | If the program is interactive, make it output a short notice like this 315 | when it starts in an interactive mode: 316 | 317 | Gnomovision version 69, Copyright (C) 19yy name of author 318 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 319 | This is free software, and you are welcome to redistribute it 320 | under certain conditions; type `show c' for details. 321 | 322 | The hypothetical commands `show w' and `show c' should show the appropriate 323 | parts of the General Public License. Of course, the commands you use may 324 | be called something other than `show w' and `show c'; they could even be 325 | mouse-clicks or menu items--whatever suits your program. 326 | 327 | You should also get your employer (if you work as a programmer) or your 328 | school, if any, to sign a "copyright disclaimer" for the program, if 329 | necessary. Here is a sample; alter the names: 330 | 331 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 332 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 333 | 334 | , 1 April 1989 335 | Ty Coon, President of Vice 336 | 337 | This General Public License does not permit incorporating your program into 338 | proprietary programs. If your program is a subroutine library, you may 339 | consider it more useful to permit linking proprietary applications with the 340 | library. If this is what you want to do, use the GNU Library General 341 | Public License instead of this License. 342 | 343 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | **************************************************************************** 2 | # ChangeLog for lesspipe.sh # 3 | **************************************************************************** 4 | Version 2.18 Feb 16 2025 5 | - short article 25 years recursive lesspipe in the lesspipe.sh wiki (en and de) 6 | - document install in user space, do no longer mention (t)csh in documentation 7 | - added xlsx2csv to first convert xlsx to csv and then display the csv content 8 | - added optional minimal version check for external commands 9 | Version 2.17 Dec 22 2024 10 | - evaluation of current locale changed (compatible with old bash versions) 11 | - correct typo in xlsx files handling 12 | Version 2.16 Nov 10 2024 13 | - security fix: check file name if used with ar 14 | - add --ansi to mdcat (fixes #48) 15 | - improved recognition of appimage files 16 | Version 2.15 Oct 03 2024 17 | - improved parsing of bat/batcat options in LESSCOLORIZE 18 | - display all certificates in pem files, not only the 1st one 19 | - do not exit if called from other programs (man, git, perldoc) 20 | - improve option processing in bat 21 | - change (again) order of programs to process man pages 22 | - force unicode/utf8 for html2text 23 | Version 2.14 Aug 16 2024 24 | - allow for colorizing dotfiles 25 | - add nvimpager as (preferred) colorizer 26 | - if installed use nvim instead of vim for coloring with vimcolor 27 | - prefer bat styles and themes from ENV or command line over config file 28 | Version 2.13 May 10 2024 29 | - added support for appimage and snap files 30 | - respect color scheme setting of vim in vimcolor, add listing of file types 31 | - improve xml (and html) display using the xmq binary 32 | - fix color detection (-R) again 33 | - support for cpio archives 34 | - fall back to 7z for supported formats 35 | Version 2.12 Mar 18 2024 36 | - improve completion for file names with special chars 37 | - better output when using xlscat 38 | - respect bat options from LESSCOLORIZER 39 | - propagate lesspipe changes to lesscomplete 40 | - don't use antiword any longer, outdated 41 | - use 7zip instead of now outdated p7zip if available 42 | - propagate file extension to newly created temporary files 43 | - use again csvlook for display of csv files if available 44 | - use csvtable for tabular display of csv files, needs Text::CSV from perl 45 | Version 2.11 Dec 13 2023 46 | - fix output of archive listings when no coloring is requested 47 | - column does not accept std input 48 | - fix html output with html2text 49 | - correct location of bash completion directory 50 | - respect csv file extension 51 | Version 2.10 Oct 05 2023 52 | - column does not accept -o option (BSD systems) 53 | - respect jsx and tsx file extension 54 | Version 2.09 Oct 03 2023 55 | - preferred display of csv files is through the program column 56 | - language recognition for bat improved 57 | - correctly recognize the -r or -R option when calling less 58 | - support for plain zlib files(used e.g. in backuppc, pdf) 59 | - fix jsx and tsx files often detected as html 60 | Version 2.08 Jun 26 2023 61 | - protect file names in tar from being used as options 62 | - support the device tree blob file format 63 | - many changes for colored output 64 | - fix template for creating a lesspipe rpm 65 | - fix vimcolor hang due to wrong vim CLI arguments 66 | - increase the file portion to read from STDIN to recognize its type 67 | - do not call mediainfo on STDIN input 68 | Version 2.07 Jan 10 2023 69 | - man page updates 70 | - support for displaying mail messages 71 | - set theme=ansi and style=plain in bat if not specified elsewhere 72 | - correct typo in less option, recognize osascript as applescript 73 | - better display of json files using jq 74 | - allow for changing the output style in bat/batcat 75 | Version 2.06 Aug 17 2022 76 | - remove perl storable (pst) files interpretation (security fix) 77 | - let bat recognize language, unless explicitly requested 78 | - call the bat colorizer with style=plain 79 | - many changes from ShellCheck for better syntax 80 | - run ShellCheck and test suite in GitHub Actions 81 | - better recognition of hdf and matlab files 82 | Version 2.05 Apr 26 2022 83 | - better colorize handling for bat and file names containing spaces 84 | - fix handling of pst (perl dump) files 85 | Version 2.04 Feb 28 2022 86 | - lessfilter can be in PATH or in homedir 87 | - csv format files get displayed in tabular form 88 | - do call ccze, mdcat only, if less accepts color seququences (-R) 89 | Version 2.03 Feb 22 2022 90 | - fix colorize handling for code2color and vimcolor 91 | - better argument parsing to detect -R option for less 92 | - use tr to convert into lower case, test string adjustments 93 | Version 2.02 Jan 19 2022 94 | - handle files names with a question mark in w3m, prefer elinks over w3m 95 | - add again perl storable support 96 | - add ~/.lessfilter support 97 | - better rpm handling, documentation enhancements 98 | - remove contrib directory, patches are partly implemented or obsolete 99 | Version 2.01 Jan 4 2022 100 | - experimental zsh and bash completion for archive contents 101 | - remove use of rpmunpack, dpkg 102 | - minor fixes in test suite and empty file handling 103 | Version 2.00 Dec 28 2021 104 | - code cleanup, minor fixes, support pygmentize with 256 color terminals 105 | Version 2.00-beta Dec 22 2021 106 | - much enhanced testsuite, can now select tests based on its number etc. 107 | - added better bsdtar handling from the contributed bsdtar patch (Jim Pryor) 108 | - added -layout option to pdftotext (Sergio Callegari) 109 | - changed code for extraction of less calling parameters 110 | - don't engage lesspipe in pipes (LESSPIPE=|-...), if called from man and perldoc 111 | - iconv handles most conversions from non foreign encodings 112 | - tarcolor enhanced and renamed to archive_color, archive listings colorizations 113 | Version 2.00-alpha Dec 8 2021 114 | - complete rewrite with many fixes and enhancements. 115 | - incompatible change: groff converts to utf8, not 'latin1' and not 'nippon' 116 | - incompatible change: do not detect perl storable file format 117 | - incompatible change: LESS_ADVANCED_PREPROCESSOR no longer honored 118 | - support for growing files and calling less from a filter (LESSOPEN=|-...) 119 | - new testsuite test.pl 120 | Version 1.91 Nov 10 2021 121 | - testsuite works, most files get colored properly 122 | Version 1.90 Oct 6 2021 123 | - make all tests in the test suite work 124 | Version 1.89 Sep 18 2021 125 | - attempts to make colorization work and pass tests in the testsuite 126 | Version 1.88 Sep 18 2021 127 | - add support for mp4 files and Jupiter notebook formats 128 | Version 1.87 Aug 7 2021 129 | - fix broken media file display (#54) 130 | - fix broken archive display (#57) 131 | - fix version number in files (#50) 132 | Version 1.86 Jul 21 2021 133 | - force html2text to use UTF8 instead of ASCII 134 | - fix pdf file type display 135 | Version 1.85 Aug 25 2020 136 | - improved MS office files handling and bug fixes 137 | Version 1.84 Jul 7 2019 138 | - support for brotli, several MS Office files and media files 139 | Version 1.83 Jul 28 2015 140 | - shortcut for unreadable files, prefer pdftotext over pdftohtml (Peter Wu) 141 | - add bsdtar as a command to unpack rar files (coadde) 142 | - add support for NetCDF and HDF5 files using ncdump, h5dump (Matt Thompson) 143 | - correctly display directories with a colon in the name (Oliver Mangold) 144 | - more conservative usage of html2text (Matt Ghali) 145 | - highlight empty inline C++ comments correctly, better pygmentize call 146 | (no - argument, use -g) (Antony Lee) 147 | - add support for Debian 2.0 files with xz packed data (Tobias Hoffmann) 148 | Version 1.82 Feb 04 2013 149 | - better handling of encodings and protect against iconv failures 150 | Version 1.81 Jan 15 2013 151 | - adjust Makefile generation, find uninstalled tarcolor in configure 152 | - add a spec file to generate a lesspipe RPM 153 | - correct LESSCOLORIZER call 154 | Version 1.80 Jan 13 2013 155 | - test.pl now shows ignored tests and missing executables, tests added/deleted 156 | - check for existence of mp3info2 to display mp3 tags 157 | - check for existence of 7zr to display 7za archives 158 | - correct bug in 1.72 for less where dir contains spaces (Tobias Hoffmann) 159 | - add colorized tar listings using tarcolor (contributed by Marc Abramowitz) 160 | - suppress the ==> lines if LESSQUIET is set (proposed by Dale Wijnand) 161 | - use alternate syntax highlighting program given by LESSCOLORIZER, currently 162 | only pygmentizer and code2color are allowed (proposed by Christian Höltje) 163 | - add directory from LESSOPEN to $PATH, look in $PATH for code2color 164 | - Introduce DESTDIR support in Makefile (requested by Michel Hermier) 165 | - the generated lesspipe.sh in the tar file has syntax highlighting enabled 166 | and no LESS_ADVANCED_PREPROCESSOR support 167 | Version 1.72 Nov 17 2011 168 | - detect proper options for the file command at runtime (Vincent Lefèvre) 169 | - guess the character encoding and optionally do a char conversion (VL) 170 | - make colored ls output working for different OS flavours (Marc Abramowitz) 171 | - do not interpret files with an extension .xml as html files (James Ahlborn) 172 | - eval `.../lesspipe.sh` will set LESS_ADVANCED_PREPROCESSOR if meaningful 173 | - sample less wrapper to open URLs with less (in contrib) (Sebastian Kayser) 174 | Version 1.71 Apr 06 2010 175 | - detect a good version of tar, try to avoid /usr/bin/tar on Solaris (Jim Pryor) 176 | - do more preprocessing if LESS_ADVANCED_PREPROCESSOR is set 177 | - always try to interpret (g)roff formatted text (man pages) 178 | - better detection of lzip and xz compressed files (Vincent Lefèvre) 179 | - do not call identify for 'image text' tagged files (Vincent Lefèvre) 180 | - do not rely on contents of LANG variable for calling iconv (Vincent Lefèvre) 181 | - have a fallback to bash or zsh for the shell used at runtime (Vincent Lefèvre) 182 | Version 1.70 Jul 16 2009 183 | - fixing the call of mktemp on MacOS (reported by Peter Kostka and Martin Otte) 184 | - detect helper programs at runtime (suggested by David Leverton, Petr Uzel) 185 | - add support for xz compression (Mathieu Bouillaguet) 186 | - more stringent tests for gzip compression 187 | - changes in rpm processing to better support MacOSX and BSD based systems 188 | - introduce --fixed in configure to statically control lesspipe generation 189 | - improved generation of Makefile 190 | - calling eval `.../lesspipe.sh` will set the ENV variable LESSOPEN properly 191 | - fixing jar processing if not using fastjar (was a bug in 1.60 only) 192 | - control amount of preprocessing by the ENV var LESS_ADVANCED_PREPROCESSOR 193 | if the related configure question is answered with y (default n) (Petr Uzel) 194 | - updated documentation to reflect recent changes 195 | Version 1.60 Dec 01, 2008 196 | - major restructuring of code, support for even more file types (Jim Pryor) 197 | - using a temp dir and mktemp for creating temporary files 198 | - concentrate file type recognition in a function 199 | - rewritten the recognition and processing of html files, added xhtml 200 | recognition, add elinks and w3m as html parsers 201 | - better support for jar files (recognized by extension jar and xpi) 202 | - support for excel and powerpoint files (recognized by extension) 203 | - support for perl pod files 204 | - colored directory listings 205 | - list and view the control parts of *.deb packages 206 | - added support for lzip compression (Antonio Diaz Diaz) 207 | - added support for DjVu files (Florian Cramer) 208 | - improved zip support for Solaris, bug fixes in configure (Paul Townsend) 209 | - code cleanup and bug fixes in lesspipe based on the restructured code 210 | - enhanced test suite 211 | - update of the documentation (merged english.txt and README) 212 | Version 1.55 Aug 13, 2008 213 | - cleanup documentation and code 214 | - remove support for unmounted media (floppy disks) 215 | - better UTF-8 support, add conversions between UTF-8 and ISO-8859-1 216 | - add preliminary support for lzma compressed files (suggested by Goetz Waschk) 217 | - better support for 7-zip files (suggestion by Stephan Hegel) 218 | - fix misspelled program name 'links' 219 | Version 1.54 Jul 25, 2008 220 | - improved support for mp3 files using id3v2 (hint from Markus Meyer) 221 | - added tests for mp3 files with id3 v1 and v2 tags 222 | - correctly recognize version of file command 223 | - prefer links over lynx for html display 224 | - add support for binary plist files (Mac OS X) (Peter D. Barnes Jr.) 225 | - add support for gpg encrypted files (Daniel Risacher) 226 | Version 1.53 Apr 11, 2006 227 | - support for Openoffice documents (Florian Cramer, Vincent Lefevre) 228 | - support for RAR archives (suggested by Cindy Leonhardt) 229 | - support for 7-zip archives, UTF-16 text (proposed by Vincent Lefevre) 230 | - support for image formats (png, gif, jpeg and others) 231 | - small shell syntax fix (reported by Andrew Barnert) 232 | - better test to recognize tar and dvi files 233 | - do not include the full path for commands if they are in the search PATH 234 | - avoid "file -" command, relax requirements for "file", Solaris now ok (Ken) 235 | - display debian files without dpkg (hint by Juergen Kahnert) 236 | - mention in BUGS that syntax highlighting is an experimental feature 237 | - require at least version 1.0 of bzip2 238 | - updated documentation 239 | Version 1.52 Jul 19, 2005 240 | - variable replacement pattern containing % causes hang in AIX, escape % char 241 | - the output of the file command can contain file names, make it harder to 242 | mislead the file type recognition logic 243 | Version 1.51 May 26, 2005 244 | - fix typo in Makefile (PREFIX instead of prefix in configure) 245 | - change consistently test syntax to be compliant to more shells 246 | - display command to set LESSOPEN env variable when called without args 247 | - fix a few cases where filenames containing space were not treated properly 248 | - add a manpage for lesspipe.sh 249 | Version 1.50 Apr 12, 2005 250 | - major changes in the processing of file names, thus allowing the display 251 | of files with white space in the name even if they are contained in archives 252 | - display of "perl storable" (see perldoc Storable) files by Slaven Rezic 253 | - many changes to get rid of minor bugs related to syntax highlighting and 254 | the display of files contained in archives 255 | - lesspipe.sh successfully tested to also work with pdksh (tested with v. 5.2) 256 | - added test suite, commands listed in TESTCMDS, test files are in testok and 257 | testnok. Can be started with make test 258 | - improved configure script and documentation enhancements 259 | 260 | Version 1.44 Dec 14, 2004 261 | ------------------------- 262 | - fixes in configure script (Slaven Rezic) 263 | - fixes for ksh syntax by Jens Schleusener 264 | 265 | Version 1.43 Dec 09, 2004 266 | ------------------------- 267 | - fixes for Cygwin by Slaven Rezic and for FreeBSD by Ben Kibbey 268 | - better recognition of perl files in code2color (Slaven Rezic) 269 | - treat Active State's PPD files as XML files (proposed by Soeren Adersen 270 | - syntax highlighting has to be enabled explicitly when calling configure 271 | - fixed bug in trap statement when using /bin/sh as shell 272 | 273 | Version 1.42 Apr 15, 2004 274 | ------------------------- 275 | - add support for Microsoft cabinet files through cabextract (v1.0 required) 276 | - correct bug (less -F was not working if used with lesspipe) 277 | 278 | Version 1.41 Apr 01, 2004 279 | ------------------------- 280 | - speed up lesspipe considerably for viewing large files (hint by Istvan Marko) 281 | - rework the syntax highlighting code (should now be compliant to more 282 | OS flavors, patches by Remi Mommsen and by WF) 283 | - support for MacOS X archives and bom files (request by Remi Mommsen) 284 | 285 | Version 1.40 Mar 21, 2004 286 | ------------------------- 287 | - introduce syntax highlighting (through 'syntax', a perl helper script) 288 | - ATTENTION: it will work only with less -r or less -R 289 | - deactivated perl support again, perldoc -m otherwise not working properly 290 | - display contents of iso images 291 | 292 | Version 1.37 Aug 08, 2003 293 | ------------------------- 294 | - perl support was not working under all circumstances 295 | 296 | Version 1.36 Aug 08, 2003 297 | ------------------------- 298 | - add new option --default in configure script 299 | - add support for perl files containing pod documentation 300 | 301 | Version 1.35 Jan 06, 2003 302 | ------------------------- 303 | - prefer pstotext over ps2ascii if available 304 | - prefer html2text over lynx if available 305 | - show the contents of mp3 files (with mp3 tags) using mp3info 306 | - for unknown (binary) data display the contained ASCII strings 307 | - support for ASCII-art (2nd version, very experimental, commented out) 308 | most of the changes proposed by Florian Cramer 309 | - corrected minor bugs in the configure script 310 | 311 | Version 1.34 May 16, 2002 312 | ------------------------- 313 | - forgotten #endif in configure 314 | - use /usr/bin/env perl to process the configure script 315 | 316 | Version 1.33 Mar 12, 2002 317 | ------------------------- 318 | - enhanced output for .deb files (proposed by Carl Greco) 319 | - default script language is bash on linux systems, ksh on all other platforms 320 | - search GNU file also under the name gfile 321 | 322 | Version 1.32 Jan 17, 2002 323 | ------------------------- 324 | - add support for Postscript and DVI format 325 | - enhance the processing of *roff files (.me and .ms files, respect LANG=jp) 326 | - added support for the files compressed with pack 327 | - minor change in RPM output 328 | (changes inspired by lessopen.sh in SuSE Linux 7.3) 329 | - change in configure script to check more precisely for shell versions 330 | 331 | Version 1.31 Dec 12, 2001 332 | ------------------------- 333 | - add option --yes to configure to allow for unattended installation 334 | - more generic Makefile (both by Philippe Defert) 335 | - corrected a bug that prevented HTML filtering for (t)csh users 336 | 337 | Version 1.30 Nov 21, 2001 338 | ------------------------- 339 | - add support for Rich Text Format (RTF). 340 | - modify the highly experimental support for ASCII-art (still commented out) 341 | both modifications proposed by Florian Cramer. 342 | - the configure script now checks for a working shell 343 | - added more extended english documentation (english.txt) 344 | 345 | Version 1.23 Oct 30, 2001 346 | ------------------------ 347 | - call ps -up to have a trailing blank after less in the ps output (case HTML) 348 | - generate a simple Makefile, rudimentary options for configure 349 | 350 | Version 1.22 Aug 10, 2001 351 | ------------------------ 352 | - w3m uses internally less, refuse to convert html unless called from less 353 | 354 | Version 1.21 Aug 8, 2001 355 | ------------------------ 356 | - recognize file type "... text executable" as text, not as binary 357 | 358 | Version 1.20 Aug 6, 2001 359 | ------------------------ 360 | - Add support for removable media via the /dev/xxx files (e.g. tar files on 361 | tapes, floppies) example: less /dev/fd0 362 | - Add support for unmounted DOS Filesystems using the mtools command suite 363 | via the /dev/xxx files. Example: less /dev/fd0 364 | - determine version of 'file' program and configure lesspipe.sh accordingly 365 | - some cleanup of configure script 366 | 367 | Version 1.10 Feb 19, 2001 368 | ------------------------- 369 | - use Lynx instead of w3m as proposed by Heinrich Kuettler 370 | - Add support for PDF-Files via 'pdftotext' by Derek B. Noonburg 371 | - Change format of first line in output (replace '='s by '==>'). 372 | - use configure to customize lesspipe.sh for local computer 373 | - add support for ASCII-art by Florian Cramer (very experimental, commented out) 374 | 375 | Version 1.05 Jan 24, 2001 376 | ------------------------- 377 | - Add support for Debian packages by Michael Wiedmann 378 | - Add MS Word support via 'antiword' by Florian Cramer 379 | 380 | Version 1.04 Jun 18, 2000 381 | ------------------------- 382 | - Variable separator character, better colon handling. 383 | 384 | Version 1.03 May 2, 2000 385 | ------------------------- 386 | - Bug in handling filenames with spaces corrected. 387 | 388 | Version 1.02 Apr 12, 2000 389 | ------------------------- 390 | - Bugs in RPM handling corrected (relocatable RPM's). 391 | 392 | Version 1.01 Mar 14, 2000 393 | ------------------------- 394 | - First attempt to handle filenames with spaces. 395 | 396 | Version 1.0 Feb 16, 2000 397 | ------------------------ 398 | - Complete rewrite to allow recursive unpacking. 399 | 400 | Version 0.4 Jan 16, 2000 401 | ------------------------ 402 | - Add RPM support, better documentation. 403 | 404 | Version 0.3 Aug 6, 1999 405 | ------------------------ 406 | - Add bzip2 support. 407 | 408 | Version 0.2 Nov 20, 1998 409 | ------------------------ 410 | - Be more verbose for special files. 411 | 412 | Version 0.1 May 25, 1997 413 | ------------------------ 414 | - First public release. 415 | 416 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | INSTALL (requires root access) 2 | ============================== 3 | Execute the following commands (for a more detailed description see below): 4 | 5 | ./configure 6 | # required 7 | cp lesspipe.sh /usr/local/bin 8 | 9 | # optional for colorizing and old openoffice writer formats 10 | cp code2color archive_color vimcolor sxw2txt lesscomplete /usr/local/bin 11 | 12 | # optional for zsh and bash completion (archive contents) 13 | cp lesscomplete /usr/local/bin # (zsh and bash) 14 | cp _less /usr/share/zsh/site-functions #(zsh) 15 | cp less_completion /etc/bashcompletion.d #(bash) 16 | 17 | or if you prefer make: 18 | 19 | make 20 | make test 21 | make install 22 | 23 | or INSTALL locally for the logged in user 24 | ========================================= 25 | # if it contains a directory writable by you (e.g. ~/bin) 26 | [[ -d ~/bin ]] || mkdir ~/bin 27 | cp lesspipe.sh lesscomplete ~/bin 28 | # optional for colorizing and old openoffice writer formats 29 | cp code2color archive_color vimcolor sxw2txt lesscomplete ~/bin 30 | 31 | # check your PATH variable, if it does not contain ~/bin then add it to PATH 32 | echo $PATH 33 | PATH=$PATH:~/bin # (put this statement in an int script .bashrc/.zshrc) 34 | 35 | # when using zsh then do similar steps for fpath and _less 36 | [[ -d ~/.fpath ]] || mkdir ~/.fpath 37 | cp _less ~/.fpath 38 | echo $fpath 39 | fpath=$fpath:~/.fpath # (put this statement in an int script .bashrc/.zshrc) 40 | 41 | # for bash the file less_completion can be put into ~/.bash_completion 42 | [[ -d ~/.bash_completion ]] || mkdir ~/.bash_completion 43 | cp less_completion ~/.bash_completion 44 | 45 | Then do set the environment variable LESSOPEN: 46 | 47 | LESSOPEN="|/usr/local/bin/lesspipe.sh %s"; export LESSOPEN #(sh like shells) 48 | 49 | DO NOT OMIT THE VERTICAL BAR AS FIRST CHARACTER, IT IS ESSENTIAL !!! 50 | 51 | Adapt the location of lesspipe.sh if you used another location. 52 | 53 | Detailed description 54 | ==================== 55 | 56 | Prerequisites: 57 | -------------- 58 | For a detailed discussion consult the file README starting with the section 59 | 'Required programs'. For best results up to date versions of 'file' 60 | tar, gzip, bzip2 and some helper programs are highly recommended. 61 | 62 | Step 1: create Makefile and lesspipe.sh using a different shell (optional) 63 | -------------------------------------------------------------------------- 64 | 65 | ./configure [--help] [--nomake] [--prefix=] [--shell=] 66 | 67 | Options: 68 | --help print this message 69 | --shell= specify full path to an alternative shell to use 70 | --nomake do not generate a Makefile 71 | Directory and file names: 72 | --prefix=PREFIX install lesspipe.sh in PREFIX/bin (/usr/local) 73 | 74 | configure generates by default a Makefile and if an alternate shell is 75 | given, then also a changed lesspipe.sh. 76 | 77 | Step 2: verify that lesspipe.sh is working correctly (optional) 78 | --------------------------------------------------------------- 79 | A test suite has been added that does cover most use cases of less. 80 | If the test suite reports "NOT ok" lines then lesspipe.sh 81 | is probably not fully functional in your environment. If the message is 82 | "ignored" it is usually an indication that some helper programs are 83 | not installed. To execute the test suite the command 84 | 85 | make test 86 | or 87 | ./test.pl [-e] [-n] [-v] [testnumber[s]] [file_name] 88 | 89 | can be used. The options -e and -v are for debugging. 90 | 91 | To get support for newer file types an additional magic file (e.g. ~/.magic, 92 | for use in the file command) might have to be created. In that case the 93 | environment variable MAGIC has to be set and has to contain both the system 94 | magic file and your personal one. Example: 95 | 96 | MAGIC='/usr/share/file/magic:/Users/myaccount/.magic' 97 | export MAGIC 98 | 99 | Step 3: install lesspipe.sh and helper programs 100 | ----------------------------------------------------------------------- 101 | You can copy lesspipe.sh to any appropriate place. 102 | Suggested location: /usr/local/bin/lesspipe.sh 103 | The helper programs can be copied to the same directory as lesspipe.sh or any 104 | directory listed in the PATH environment variable. 105 | The archive_color script allows to colorize tar and other archive listings. 106 | 107 | cp lesspipe.sh code2color vimcolor sxw2txt archive_color /usr/local/bin 108 | 109 | This can be achieved also with make: 110 | 111 | make install [PREFIX=] # that copies the scripts to /bin 112 | 113 | For zsh and bash a tab completion for archive contents is provided. 114 | To activate it the script lesscomplete has to be copied into a directory 115 | listed in $PATH, the _less function for zsh into a directory listed in $fpath 116 | and less_completion into a directory used by its completion mechanism. 117 | 118 | Step 4: activate lesspipe.sh 119 | ---------------------------- 120 | To make use of the installed lesspipe.sh set the environment variable LESSOPEN: 121 | The use of the fully qualified path is recommended to avoid problems with other 122 | scripts of the same name or with a changed search PATH: 123 | 124 | LESSOPEN="|/usr/local/bin/lesspipe.sh %s"; export LESSOPEN #(sh like shells) 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lesspipe.sh, a preprocessor for less 2 | 3 | Version: 2.18 4 | Author : Wolfgang Friebel [wp.friebel@gmail.com](mailto://wp.friebel@gmail.com) 5 | License: GPL 6 | 7 | Latest version available as: 8 | [zip file on github](https://github.com/wofr06/lesspipe.sh/archive/lesspipe.zip) 9 | and the [repository on github](https://github.com/wofr06/lesspipe) 10 | 11 | The development version can be cloned using git: 12 | `git clone https://github.com/wofr06/lesspipe.git` 13 | To report bugs or make proposals to improve lesspipe please contact 14 | the author by email. 15 | 16 | ## Contents 17 | 18 | 0. Motivation 19 | 1. Introduction 20 | 2. Usage 21 | 3. Required programs 22 | 4. Supported file formats 23 | - Supported compression methods and archive formats 24 | - List of preprocessed file types 25 | - Conversion of files with alternate character encoding 26 | 5. Colorizing the output 27 | - Syntax highlighting 28 | - Syntax highlighting choices 29 | - List of supported languages 30 | - Colored Directory listing 31 | - Colored listing of tar file contents 32 | 6. Calling less from standard input 33 | 7. Displaying files with special characters in the file name 34 | 8. Tab completion for zsh and bash 35 | 9. User defined filtering 36 | 10. Debugging 37 | 11. (Old) documentation about lesspipe 38 | 12. External links 39 | - URLs to some utilities 40 | - References 41 | 13. Contributors 42 | 43 | ## 0. Motivation 44 | 45 | If you use 46 | 47 | - the pager `less` in the command line, 48 | - the version control system `git`, 49 | - the text editor `Vim` or 50 | - the mail client `mutt`, 51 | 52 | then lesspipe.sh enables these programs to *read* non-text files, such as: 53 | 54 | - PDFs, 55 | - (Microsoft or LibreOffice) Office documents, or even 56 | - media (such as JPG or PNG images, MP3 audio or video) files 57 | 58 | where *read* means, 59 | 60 | - (format and) show the contained text (of a document or tag in a media file), 61 | or 62 | - show sensible file information (such as length of the video). 63 | 64 | To enable `less` respectively `git`, `Vim` or `mutt` to read non-text files by 65 | lesspipe.sh, see 66 | 67 | - Section 2 on the Usage of lesspipe.sh, respectively 68 | - the Wiki at https://github.com/wofr06/lesspipe/wiki 69 | 70 | For the text and info extraction, lesspipe.sh will depend on external tools, 71 | but many use cases are covered by an installation of 72 | 73 | - LibreOffice and a common text browser (such as `lynx`), 74 | - pdftotext, and 75 | - mediainfo (or exiftool). 76 | 77 | ## 1. Introduction 78 | 79 | To browse files under UNIX the excellent viewer less [1] can be used. By 80 | setting the environment variable **LESSOPEN**, less can be enhanced by external 81 | filters to become even more powerful. Most Linux distributions come already 82 | with a "lesspipe.sh" that covers the most common situations. 83 | 84 | The input filter for less described here is called "lesspipe.sh". It is able 85 | to process a wide variety of file formats. It enables users to deeply inspect 86 | archives and to display the contents of files in archives without having to 87 | unpack them before. That means file contents can be properly interpreted even 88 | if the files are compressed and contained in a hierarchy of archives (often 89 | found in RPM or DEB archives containing source tarballs). The filter is easily 90 | extensible for new formats. 91 | 92 | The input filter which is also called "lesspipe.sh" is a bash script, but 93 | works as well as a zsh script. 94 | 95 | The filter does different things depending on the file format. In most cases 96 | it is determined on the output of the `file --mime` command [2], that 97 | returns the mime type. In some cases the mime type is too unspecific and then 98 | the `file` command yielding a textual description or the file suffix is used 99 | to determine what to display. 100 | 101 | By default less wraps long lines unless called with the option -S or 102 | --chop-long-lines. That can be changed interactively by typing -S followed by 103 | ENTER when viewing files with long lines. It is e.g. quite useful for tabular 104 | display of csv files with many columns. 105 | 106 | ## 2. Usage 107 | 108 | (see also the man page lesspipe.1) 109 | 110 | To activate lesspipe.sh the environment variable **LESSOPEN** has to be defined 111 | in the following way: 112 | ``` 113 | LESSOPEN="|lesspipe.sh %s"; export LESSOPEN # (sh like shells) 114 | ``` 115 | If `lesspipe.sh` is not in the UNIX search path or if the wrong `lesspipe.sh` is 116 | found in the search path, then the full path to `lesspipe.sh` should be given 117 | in the above commands. The above commands work only in the described manner 118 | if the file name is lesspipe.sh. 119 | 120 | If it is installed under a different name then calling it without an argument 121 | will work as a filter with LESSQUIET set and expecting input from STDIN. 122 | 123 | The command to set **LESSOPEN** can also be displayed by calling `lesspipe.sh` 124 | without arguments. This can even be used to set **LESSOPEN** directly: 125 | ``` 126 | eval "$(lesspipe.sh)" # (bash) or 127 | lesspipe.sh | source /dev/stdin # (zsh) 128 | ``` 129 | Several Linux distributions do now set **LESSOPEN** by default and if the contents of the variable is not referring to this lesspipe.sh version, it has to be redefined to get the functionality described here. 130 | 131 | As `lesspipe.sh` is accepting only a single argument, a hierarchical list of file 132 | names has to be separated by a non-blank character. A colon is rarely found 133 | in file names, therefore it has been chosen as the separator character. If a 134 | file name does however contain at least one isolated colon, the equal sign = 135 | can be used as an alternate separator character. At each stage in 136 | extracting files from such a hierarchy, the file type is determined. This 137 | guarantees a correct processing and display at each stage of the filtering. 138 | 139 | To view files in archives, the following command can be used: 140 | ``` 141 | less archive_file:contained_file 142 | ``` 143 | This can be used to extract files from an archive: 144 | ``` 145 | less archive_file:contained_file > extracted_file 146 | ``` 147 | For extracting files less is not required, that can be done also using: 148 | ``` 149 | lesspipe.sh archive_file:contained_file > extracted_file 150 | ``` 151 | Even a file in an archive, that itself is contained in yet 152 | another archive can be viewed this way: 153 | ``` 154 | less super_archive:archive_file:contained_file 155 | ``` 156 | The script is able to extract files up to a depth of 6 where applying a 157 | decompression algorithm counts as a separate level. In a few rare cases, the 158 | file command does not recognize the correct format. 159 | In such cases, the filtering can be suppressed by a trailing colon on the file 160 | name. That can also be used to output the original unmodified file or to 161 | suppress syntax highlighting (see below). 162 | 163 | Several environment variables can influence the behavior of lesspipe.sh. 164 | 165 | **LESSQUIET** will suppress additional output not belonging to the file contents 166 | if set to a non-empty value. 167 | 168 | **LESS** can be used to switch on colored less output (should contain -R). 169 | 170 | **LESSCOLORIZER** can be set to prefer a highlighting program from the following 171 | choices (`nvimpager` `bat` `batcat` `pygmentize` `source-highlight` `vimcolor` `code2color`). 172 | Otherwise the first program in that list that is installed will be used, with the caveat that `bat` will use [ansi theme](https://github.com/wofr06/lesspipe/issues/155#issuecomment-2312972276) instead of its default colors. 173 | 174 | ## 3. Required programs 175 | 176 | Most of the programs are checked for its existence before they get called 177 | in lesspipe.sh. However some of the programs are assumed to always be 178 | installed. That is foremost `bash` or `zsh` (have the appropriate first line 179 | in the script), then `file` and other utilities like `cat`, 180 | `grep`, `ln`, `ls`, `mkdir`, `rm`, `strings`, `tar` and `tr`. 181 | For testing lesspipe.sh `perl` is used, that is however not 182 | required in just using `lesspipe.sh`. 183 | 184 | ## 4. Supported file formats 185 | 186 | Currently `lesspipe.sh` [3] supports the following compression methods 187 | and file types (i.e. the file contents gets transformed by `lesspipe.sh`): 188 | 189 | ### 4.1 Supported compression methods and archive formats 190 | - gzip, compress requires `gzip` 191 | - bzip2 requires `bzip2` 192 | - lzma requires `lzma` or `7z` 193 | - xz requires `xz` or `7z` 194 | - zstd requires `zstd` 195 | - brotli requires `bro` 196 | - lz4 requires `lz4` 197 | - tar requires optionally `archive_color` for colorizing 198 | - ar library requires `bsdtar` or `ar` 199 | - zip archive requires `bsdtar` or `unzip` 200 | - jar archive requires `bsdtar` or `unzip` 201 | - rar archive requires `bsdtar` or `unrar` or `rar` 202 | - 7-zip archive requires `7zz` or `7zr` or `7z` or `7za` 203 | - lzip archive requires `lzip` 204 | - iso images requires `bsdtar` or `isoinfo` or `7z` 205 | - rpm requires `rpm2cpio` and `cpio` or `bsdtar` 206 | - Debian requires `bsdtar` or `ar` 207 | - cab requires `cabextract` or `7z` 208 | - cpio requires `cpio` or `bsdtar` or `7z` 209 | - appimage requires `unsquashfs` 210 | - snap requires `snap` and `unsquashfs` 211 | 212 | ### 4.2 List of preprocessed file types 213 | - directory displayed using `ls -lA` 214 | - nroff(man) requires `mandoc` or `man` or `groff` 215 | - shared library requires `nm` 216 | - MS Word (doc) requires `wvText` or `catdoc` or `libreoffice` 217 | - Powerpoint (ppt) requires `catppt` 218 | - Excel (xls) requires `in2csv` (csvkit) or `xls2csv` 219 | - odt requires `pandoc` or `odt2txt` or `libreoffice` 220 | - odp requires `libreoffice` 221 | - ods requires `xlscat` or `libreoffice` 222 | - MS Word (docx) requires `pandoc` or `docx2txt` or `libreoffice` 223 | - Powerpoint (pptx) requires `pptx2md` or `libreoffice` 224 | - Excel (xlsx) requires `xlsx2csv` (>= 0.8.3) or `in2csv` or `xlscat` or `excel2csv` or `libreoffice` 225 | - csv requires `csvtable` or `csvlook` or `column` or `pandoc` 226 | - rtf requires `unrtf` or `libreoffice` 227 | - epub requires `pandoc` 228 | - html,xml requires one of `xmq`, `w3m`, `lynx`, `elinks` or `html2text` 229 | - pdf requires `pdftotext` or `pdftohtml` 230 | - perl pod requires `pod2text` or `perldoc` 231 | - dvi requires `dvi2tty` 232 | - djvu requires `djvutxt` 233 | - ps requires `ps2ascii` (from the gs package) 234 | - mp3 requires `ffprobe` or `eyeD3` or `id3v2` 235 | - multimedia formats requires `ffprobe` or `mediainfo` or `exiftools` 236 | - image formats requires `mediainfo` or `exiftools` or `identify` 237 | - hdf, nc4 requires `h5dump` or `ncdump` (NetCDF format) 238 | - crt, pem, csr, crl requires `openssl` 239 | - matlab requires `matdump` 240 | - Jupyter notebook requires `pandoc` 241 | - markdown requires `mdcat` or `pandoc` 242 | - log requires `ccze` 243 | - java.class requires `procyon` 244 | - MacOS X plist requires `plistutil` 245 | - binary data requires `strings` 246 | - json requires `jq` 247 | - device tree blobs requires `dtc` (extension dtb or dts) 248 | 249 | Files in the html, xml and perl pod format are always rendered. Sometimes 250 | however the original contents of the file should be viewed instead. 251 | That can be achieved by appending a colon to the file name. If the correct 252 | file type (html, xml, pod) follows, the output can get colorized (see also 253 | the section below). 254 | 255 | If the binary xmq is installed, then xml is rendered differently, so that 256 | the xml structure is better recognized. A similar display for html contents 257 | using xmq is achieved by appending a colon to the file name. To get the 258 | original html file contents, two colons are required in this case. 259 | 260 | ### 4.3 Conversion of files with alternate character encoding 261 | If the file utility reports text with an encoding different from the one 262 | used in the terminal, then the text will be transformed using `iconv` into 263 | the default encoding. This does assume the file command gets the file 264 | encoding right, which can be wrong in some situations. An appended colon 265 | to the file name does suppress the conversion. 266 | 267 | ## 5. Colorizing the output 268 | 269 | Syntax highlighting and other methods of colorizing the output 270 | is only activated if the environment variable **LESS** is existing and contains 271 | the option -R (or -r) or less is called with one of these options. 272 | 273 | The display of wrapped long lines and moving backward in a file using the 274 | option -r can give weird output and is not recommended. For an explanation see 275 | http://www.greenwoodsoftware.com/less/faq.html#dashr 276 | 277 | ### 5.1 Syntax highlighting 278 | Syntax highlighting is not always wanted, it can be switched off by 279 | appending a colon after the file name. If the wrong language was chosen 280 | for syntax highlighting or no language was recognized, then the correct 281 | one can be forced by appending a colon and a suffix to the file name as 282 | follows (assuming plfile is a file with perl syntax): 283 | ``` 284 | less plfile:pl or less plfile:perl (depending on the colorizer) 285 | ``` 286 | #### 5.1.1 Syntax highlighting choices 287 | The filter is able to do syntax highlighting for a wide variety of file 288 | types. If installed, `nvimpager` is used for colorizing the output. If 289 | not, `bat`/`batcat`, `pygmentize`, `source-highlight`, `code2color` 290 | and `vimcolor` are 291 | tried. Among these colorizers a preferred one can be forced for colorizing 292 | by setting the ENV variable **LESSCOLORIZER** to the name of the colorizer. 293 | For `pygmentize` and `bat/batcat` a restricted set of options can be added: 294 | ``` 295 | LESSCOLORIZER='pygmentize -O style=foo' 296 | LESSCOLORIZER='bat --style=foo --theme=bar' # --theme=default for default theme 297 | ``` 298 | Much better syntax highlighting is obtained using the `less` emulation of `vim`: 299 | The editor `vim` comes with a file `less.sh`, e.g. on Ubuntu located in 300 | /usr/share/vim/vimXX/macros (XX being the version number). Assuming that file 301 | location, a function `lessc` (bash, zsh, ksh users) 302 | ``` 303 | lessc () { /usr/share/vim/vimXX/macros/less.sh "$@"} 304 | ``` 305 | is defined and `lessc filename` is used to view the colorful file contents. 306 | The same can be achieved using less and `vimcolor`, but that is much slower. 307 | 308 | #### 5.1.2 List of supported languages 309 | To see which languages are supported the list can be printed using the 310 | following colorizer commands: 311 | 312 | ``` 313 | bat --list-languages 314 | batcat --list-languages 315 | pygmentize -L lexers 316 | source-highlight --lang-list 317 | code2color -h 318 | vimcolor -L (both for vimcolor and nvimpager) 319 | ``` 320 | 321 | ### 5.2 Colored Directory listing 322 | Depending on the operating system ls is called with appropriate options to 323 | produce colored output. 324 | 325 | ### 5.3 Colored listing of tar file contents 326 | If the executable archive_color is installed, then the listing of tar file 327 | contents is colored in a similar fashion as directory contents. 328 | 329 | ## 6. Calling less from standard input 330 | 331 | Normally `lesspipe.sh` is not called when less is used within a pipe, such as 332 | ``` 333 | cat somefile | less 334 | ``` 335 | This restriction is removed when the **LESSOPEN** variable starts with the 336 | characters |- or ||-. 337 | Then the colon notation for extracting and displaying files in archives 338 | does not work. As a way out `lesspipe.sh` analyses the command line and looks 339 | for the last argument given to less. If it starts with a colon, it is 340 | interpreted from `lesspipe.sh` as a continuation of the first parameter. 341 | Examples: 342 | ``` 343 | cat some_c_file | less - :c # equivalent to less some_c_file:c 344 | cat archive | less - :contained_file # extracts a file from the archive 345 | ``` 346 | ## 7. Displaying files with special characters in the file name 347 | 348 | Shell meta characters in file names: space (frequently used in windows 349 | file names), 350 | 351 | the characters | & ; ( ) ` < > " ' # ~ = $ * ? [ ] or \\ 352 | 353 | must be escaped by a \ when used in the shell, e.g. `less a\ b.tar.gz:a\\"b` 354 | will display the file a"b contained in the gzipped tar archive a b.tar.gz. 355 | 356 | ## 8. Tab completion for zsh and bash 357 | 358 | An existing `zsh` completion script has been enhanced to provide tab completion 359 | within archives, similar to what is possible with the `tar` command completion. 360 | A `bash` completion script has been modeled loosely after the `zsh` completion. 361 | 362 | In both shells it is now possible to complete contents of archive format files 363 | such as tar, zip, rpm, deb files etc. This works as well in compressed files 364 | (e.g. tar.gz) and in chained archives, e.g.in source rpm files containing 365 | tar.gz files. 366 | 367 | To make it work, the script `lesscomplete` has to be executable and must be 368 | found in one of the directories listed in the `$PATH` environment variable. 369 | For zsh the file `_less` has to be stored in one of the directories listed in 370 | `$fpath` or the directory containing `_less` has to be added to `$fpath`, e.g. 371 | by: 372 | ``` 373 | fpath=(~/zsh_functions $fpath) 374 | ``` 375 | In bash, the function `less_completion` has to be added to the shell environment 376 | by sourcing the script (e.g. from .bashrc using the correct location): 377 | ``` 378 | source ~/bash_functions/less_completion 379 | ``` 380 | 381 | The completion mechanism is triggered after entering a colon or an equal sign 382 | as for example in 383 | 384 | ``` 385 | less archive_file: # and then 386 | less archive_file:partial_result 387 | less archive_file:contained_archive: # etc. 388 | ``` 389 | ## 9. User defined filtering 390 | 391 | The lesspipe.sh filtering can be replaced or enhanced by a user defined 392 | program. Such a program has to be called either `.lessfilter` (and be placed in 393 | the user's home directory), or `lessfilter` (and be accessible from a directory 394 | mentioned in the environment variable `PATH`). 395 | That program has to be executable and has to end with an exit code 0, if the 396 | filtering was done within that script. Otherwise, a nonzero exit code means 397 | the filtering is left to lesspipe.sh. 398 | 399 | This mechanism can be used to add filtering for new formats or e.g. inhibit 400 | filtering for certain file types. 401 | 402 | ## 10. Debugging 403 | 404 | If the script does not work as expected for a given file contents, one could 405 | try to output the commands executed by lesspipe.sh. That is achieved by 406 | 407 | ``` 408 | bash -x lesspipe.sh file_name > /dev/null # or zsh -x 409 | ``` 410 | It is also possible setting temporarily the **LESSOPEN** variable to e.g. 411 | ``` 412 | LESSOPEN='|bash -x /usr/local/bin/lesspipe.sh %s' 413 | ``` 414 | and then use `less` with the file to be displayed. The normal output goes to 415 | STDOUT and the commands executed to STDERR. 416 | 417 | ## 11. (Old) documentation about lesspipe 418 | 419 | In English 420 | - https://ref.web.cern.ch/CERN/CNL/2002/001/unix-less/ 421 | - https://www.oreilly.com/library/view/bash-cookbook/0596526784/ch08s15.html 422 | - https://github.com/wofr06/lesspipe/wiki/article_en 423 | 424 | In German: 425 | - german.txt (distributed with lesspipe, not updated) 426 | - https://www.linux-magazin.de/ausgaben/2001/01/bessere-sicht/ 427 | - https://www.linux-community.de/ausgaben/linuxuser/2002/04/lesspipe/ 428 | - https://www.linux-magazin.de/ausgaben/2022/07/lesspipe-2-0/ 429 | - https://github.com/wofr06/lesspipe/wiki/article_de 430 | 431 | ## 12. External links 432 | 433 | (last checked: Oct 20 2024): 434 | 435 | ### 12.1 URLs to some utilities (with last known release) 436 | - 7zz https://sourceforge.net/projects/sevenzip/ (2024) 437 | - 7zr (outdated!) https://sourceforge.net/projects/p7zip/ (2016) 438 | - cabextract https://www.cabextract.org.uk/ (2023) 439 | - catdoc,catppt,xls2csv https://www.wagner.pp.ru/~vitus/software/catdoc/ (2016) 440 | - ccze https://github.com/software-revive/ccze-rv (2020) 441 | - csvtable https://github.com/wofr06/csvtable (2024) 442 | - djvutxt https://djvu.sourceforge.net/ (2020) 443 | - docx2txt https://docx2txt.sourceforge.net/ (2014) 444 | - dvi2tty https://www.ctan.org/tex-archive/dviware/dvi2tty/ (2016) 445 | - excel2csv https://github.com/informationsea/excel2csv (2018) 446 | - html2text https://github.com/grobian/html2text (2024) 447 | - id3v2 https://id3v2.sourceforge.net/ (2010) 448 | - lzip https://www.nongnu.org/lzip/lzip.html (2024) 449 | - matdump https://sourceforge.net/projects/matio/ (2024) 450 | - mediainfo https://mediaarea.net/MediaInfo/ (2024) 451 | - odt2txt https://github.com/dstosberg/odt2txt (2017) 452 | - pandoc https://pandoc.org/ (2024) 453 | - pptx2md https://github.com/ssine/pptx2md (2024) 454 | - tarcolor https://github.com/msabramo/tarcolor (2014) 455 | - archive_color modified version of tarcolor (contained in this package) 456 | - unrtf https://www.gnu.org/software/unrtf/ (2018) 457 | - wvText https://github.com/AbiWord/wv/ (2014) 458 | - xlscat https://metacpan.org/pod/Spreadsheet::Read (2024) 459 | - xlsx2csv (>=0.8.3) https://github.com/dilshod/xlsx2csv (2024) 460 | - sxw2txt https://vinc17.net/software/sxw2txt (2010) 461 | - dtc https://git.kernel.org/cgit/utils/dtc/dtc.git (2024) 462 | - xmq https://github.com/libxmq/xmq/releases/latest (2024) 463 | - nvimpager https://github.com/lucc/nvimpager (2024) 464 | 465 | ### 12.2 References 466 | - [1] http://www.greenwoodsoftware.com/less/ (less) 467 | - [2] http://www.darwinsys.com/file/ (file) 468 | - [3] https://github.com/wofr06/lesspipe 469 | - [5] http://www.palfrader.org/code2html/ (code2html) 470 | 471 | ## 13. Contributors 472 | 473 | The script lesspipe.sh is constantly enhanced by suggestions from users and 474 | reporting bugs or deficiencies. Thanks to (in alphabetical order): 475 | (contributors after Sep 2015 see github history) 476 | 477 | Marc Abramowitz, James Ahlborn, Sören Andersen, Andrew Barnert, 478 | Peter D. Barnes, Jr., Eduard Bloch, Mathieu Bouillaguet, Florian Cramer, 479 | Philippe Defert, Antonio Diaz Diaz, Bastian Fuchs, Matt Ghali, Carl Greco, 480 | Stephan Hegel, Michel Hermier, Tobias Hoffmann, Christian Höltje, 481 | Jürgen Kahnert, Sebastian Kayser, Ben Kibbey, Peter Kostka, 482 | Heinrich Kuettler, Antony Lee, Vincent Lefèvre, David Leverton, Jay Levitt, 483 | Vladimir Linek, Oliver Mangold, Istvan Marko, Markus Meyer, Remi Mommsen, 484 | Derek B. Noonburg, Martin Otte, Jim Pryor, Slaven Rezic, Daniel Risacher, 485 | Jens Schleusener, Ken Teague, Matt Thompson, Paul Townsend, Petr Uzel, 486 | Chelban Vasile, Götz Waschk, Michael Wiedmann, Dale Wijnand, Peter Wu. 487 | -------------------------------------------------------------------------------- /_less: -------------------------------------------------------------------------------- 1 | #compdef less -value-,LESS,-default- -value-,LESSCHARSET,-default- 2 | 3 | local curcontext="$curcontext" fgbg=foreground res slash fn _list ret=1 4 | local -a state line expl files basic suf 5 | 6 | case $service in 7 | *LESSCHARSET*) 8 | _wanted charsets expl 'character set' compadd ascii iso8859 latin1 latin9 \ 9 | dos ebcdic IBM-1047 koi8-r next utf-8 10 | return 11 | ;; 12 | *LESS*) 13 | compset -q 14 | words=( fake "$words[@]" ) 15 | (( CURRENT++ )) 16 | ;; 17 | *) 18 | if compset -P '*:' || compset -P '*='; then 19 | fn="${words[2]%[:=]*}" 20 | res=$(lesscomplete "$fn") 21 | while read -r line; do 22 | [[ $line == /* ]] && slash=1 23 | _list+=("$line" "$line") 24 | done <<< "$res" 25 | [[ -n $slash ]] && compadd -x '%Bplease type / to complete top directory' 26 | _wanted files expl 'files from archive' _multi_parts / _list 27 | else 28 | files=( '*:file:_files' ) 29 | fi 30 | ;; 31 | esac 32 | 33 | if compset -P '+[-0-9]#'; then 34 | _describe 'less command' '( 35 | g:goto\ line 36 | F:scroll\ to\ end\ and\ keep\ reading\ file 37 | G:go\ to\ end\ of\ file 38 | %:go\ to\ position\ in\ file 39 | p:go\ to\ position\ in\ file 40 | )' 41 | return 42 | fi 43 | 44 | _arguments -S -s -A "[-+]*" \ 45 | '(-? --help)'{-\?,--help}'[display summary of less commands]' \ 46 | '(-a --search-skip-screen)'{-a,--search-skip-screen}'[skip current screen in searches]' \ 47 | '(-A --SEARCH-SKIP-SCREEN)'{-A,--SEARCH-SKIP-SCREEN}"[start searches just after target line]" \ 48 | '(-b --buffers)'{-b+,--buffers=}'[specify amount of buffer space used for each file]:buffer space (kilobytes)' \ 49 | '(-B --auto-buffers)'{-B,--auto-buffers}"[don't automatically allocate buffers for pipes]" \ 50 | '(-C --CLEAR-SCREEN -c --clear-screen)'{-c,--clear-screen}'[repaint screen instead of scrolling]' \ 51 | '!(-c --clear-screen)'{-C,--CLEAR-SCREEN} \ 52 | '(-d --dumb)'{-d,--dumb}'[suppress error message if terminal is dumb]' \ 53 | '*'{-D+,--color=}'[set screen colors]: :->colors' \ 54 | '(-e -E --quit-at-eof --QUIT-AT-EOF)'{-e,--quit-at-eof}'[exit the second time end-of-file is reached]' \ 55 | '(-e -E --quit-at-eof --QUIT-AT-EOF)'{-E,--QUIT-AT-EOF}'[exit when end-of-file is reached]' \ 56 | '(-f --force)'{-f,--force}'[force opening of non-regular files]' \ 57 | '(-F --quit-if-one-screen)'{-F,--quit-if-one-screen}'[exit if entire file fits on first screen]' \ 58 | '(-G --HILITE-SEARCH -g --hilite-search)'{-g,--hilite-search}'[highlight only one match for searches]' \ 59 | '(-g --hilite-search -G --HILITE-SEARCH)'{-G,--HILITE-SEARCH}'[disable highlighting of search matches]' \ 60 | '(-h --max-back-scroll)'{-h+,--max-back-scroll=}'[specify backward scroll limit]:backward scroll limit (lines)' \ 61 | '(-I --IGNORE-CASE -i --ignore-case)'{-i,--ignore-case}'[ignore case in searches that lack uppercase]' \ 62 | '(-i --ignore-case -I --IGNORE-CASE)'{-I,--IGNORE-CASE}'[ignore case in all searches]' \ 63 | '(-j --jump-target)'{-j+,--jump-target}'[specify screen position of target lines]:position (line)' \ 64 | '(-J --status-column)'{-J,--status-column}'[display status column on the left]' \ 65 | \*{-k+,--lesskey-file=}'[use specified lesskey file]:lesskey file:_files' \ 66 | '(-K --quit-on-intr)'{-K,--quit-on-intr}'[exit less in response to ctrl-c]' \ 67 | '(-L --no-lessopen)'{-L,--no-lessopen}'[ignore the LESSOPEN environment variable]' \ 68 | '(-M --LONG-PROMPT -m --long-prompt)'{-m,--long-prompt}'[prompt verbosely]' \ 69 | '(-m --long-prompt -M --LONG-PROMPT)'{-M,--LONG-PROMPT}'[prompt very verbosely]' \ 70 | '(-N --LINE-NUMBERS -n --line-numbers)'{-n,--line-numbers}"[don't keep track of line numbers]" \ 71 | '(-n --line-numbers -N --LINE-NUMBERS)'{-N,--LINE-NUMBERS}'[show line numbers]' \ 72 | '(* -O --LOG-FILE -o --log-file)'{-o+,--log-file=}'[copy input to file]:file:_files' \ 73 | '(* -o --log-file -O --LOG-FILE)'{-O+,--LOG-FILE=}'[copy input to file, overwriting if necessary]:file:_files' \ 74 | '(-p --pattern)'{-p+,--pattern=}'[start at specified pattern]:pattern' \ 75 | \*{-P+,--prompt=}'[specify prompt format]:prompt:->prompts' \ 76 | '(-Q --QUIET --SILENT -q --quiet --silent)'{-q,--quiet,--silent}'[never use bell]' \ 77 | '(-q --quiet --silent -Q --QUIET --SILENT)'{-Q,--QUIET,--SILENT}'[limit use of bell]' \ 78 | '(-r -R --raw-control-chars --RAW-CONTROL-CHARS)'{-r,--raw-control-chars}'[display raw control characters]' \ 79 | '(-r -R --raw-control-chars --RAW-CONTROL-CHARS)'{-R,--RAW-CONTROL-CHARS}'[display control chars; keep track of screen effects]' \ 80 | '(-s --squeeze-blank-lines)'{-s,--squeeze-blank-lines}'[squeeze consecutive blank lines down to one]' \ 81 | '(-S --chop-long-lines)'{-S,--chop-long-lines}'[truncate long lines instead of folding]' \ 82 | '(-t --tag)'{-t+,--tag=}'[edit file containing tag]:tag:->tags' \ 83 | '(-T --tag-file)'{-T+,--tag-file=}'[specify tags file]:tags file:_files' \ 84 | '(-u --underline-special)'{-u,--underline-special}'[send backspaces and carriage returns to the terminal]' \ 85 | '(-U --UNDERLINE-SPECIAL)'{-U,--UNDERLINE-SPECIAL}'[treat backspaces, tabs and carriage returns as control characters]' \ 86 | '(* -)'{-V,--version}'[display version information]' \ 87 | '(-W --HILITE-UNREAD -w --hilite-unread)'{-w,--hilite-unread}'[highlight first unread line after forward page]' \ 88 | '(-w --hilite-unread -W --HILITE-UNREAD)'{-W,--HILITE-UNREAD}'[highlight first unread line after forward movement]' \ 89 | '(-x --tabs)'{-x+,--tabs=}'[set tab stops]:tab stops' \ 90 | '(-X --no-init)'{-X,--no-init}'[disable use of terminal init string]' \ 91 | '--no-keypad[disable use of keypad terminal init string]' \ 92 | '(-y --max-forw-scroll)'{-y,--max-forw-scroll}'[specify forward scroll limit]' \ 93 | '(-z --window)'{-z+,--window=}'[specify scrolling window size]:lines' \ 94 | '(-\" --quotes)'{'-\"+',--quotes=}'[change quoting character]:quoting characters' \ 95 | '(-~ --tilde)'{-~,--tilde}"[don't display tildes after end of file]" \ 96 | '(-\# --shift)'{'-\#+',--shift=}"[specify amount to move when scrolling horizontally]:number" \ 97 | '--file-size[automatically determine the size of the input file]' \ 98 | '--incsearch[search file as each pattern character is typed in]' \ 99 | '--line-num-width=[set the width of line number field]:width [7]' \ 100 | '--follow-name[the F command changes file if the input file is renamed]' \ 101 | '--mouse[enable mouse input]' \ 102 | '--no-histdups[remove duplicates from command history]' \ 103 | '--rscroll=[set the character used to mark truncated lines]:character [>]' \ 104 | '--save-marks[retain marks across invocations of less]' \ 105 | '--status-col-width=[set the width of the -J status column]:width [2]' \ 106 | '--use-backslash[subsequent options use backslash as escape char]' \ 107 | '--use-color[enable colored text]' \ 108 | '--wheel-lines=[specify lines to move for each click of the mouse wheel]:lines' \ 109 | "$files[@]" && ret=0 110 | 111 | 112 | if [[ -n "$state" ]]; then 113 | case $state in 114 | colors) 115 | if compset -P 1 \?; then 116 | [[ $IPREFIX[-1] != [a-z] ]] || compset -P 1 + || _describe 'color application' '( +:add\ to\ existing\ attribute )' 117 | suf=( -S '' ) 118 | compset -P 1 '([-a-zA-Z]|*.)' && fgbg=background && suf=() 119 | basic=( B:blue C:cyan G:green K:black M:magenta R:red W:white Y:yellow ) 120 | _describe -t colors "$fgbg color" \ 121 | "( -:default ${(j. .)${(@)basic/:/:light\ }} ${(Lj. .)basic} )" "$suf[@]" && ret=0 122 | else 123 | _describe 'text' '( 124 | B:binary\ characters 125 | C:control\ characters 126 | E:errors\ and\ informational\ messages 127 | M:mark\ letters\ in\ the\ status\ column 128 | N:line\ numbers\ enabled\ via\ the\ -N\ option 129 | P:prompts 130 | R:the\ rscroll\ character 131 | S:search\ results 132 | W:the\ highlight\ enabled\ via\ the\ -w\ option 133 | d:bold\ text 134 | k:blinking\ text 135 | s:standout\ text 136 | u:underlined\ text 137 | )' -S '' && ret=0 138 | fi 139 | ;; 140 | prompts) 141 | if compset -p 1; then 142 | _message -e prompt 143 | else 144 | _describe 'prompt' '( 145 | s:short\ prompt 146 | m:medium\ prompt 147 | M:long\ prompt 148 | h:help\ screen\ prompt 149 | \=:\=\ command\ prompt 150 | w:waiting\ prompt 151 | )' && ret=0 152 | fi 153 | ;; 154 | tags) 155 | if (( $+LESSGLOBALTAGS )); then 156 | _global_tags && ret=0 157 | else 158 | _ctags_tags && ret=0 159 | fi 160 | ;; 161 | esac 162 | fi 163 | 164 | return ret 165 | -------------------------------------------------------------------------------- /archive_color: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # archive_color (by Wolfgang Friebel), a slightly enhanced tarcolor 4 | # changed date recognition (allow for localized dates), works also for 5 | # other archive listings, such as rpm, debian, ar, isoinfo archives 6 | # 7 | # originally by Marc Abramowitz 8 | # 9 | # https://github.com/msabramo/tarcolor 10 | # 11 | # Colors output of `tar tvf` similarly to the way GNU ls (in GNU 12 | # coreutils) would color a directory listing. 13 | # 14 | # Colors can be customized using an environment variable: 15 | # 16 | # TAR_COLORS='di=01;34:ln=01;36:ex=01;32:so=01;40:pi=01;40:bd=40;33:cd=40;33:su=0;41:sg=0;46' 17 | # 18 | # The format for TAR_COLORS is similar to the format used by LS_COLORS 19 | # Check out the online LSCOLORS generator at http://geoff.greer.fm/lscolors/ 20 | 21 | use warnings; 22 | use strict; 23 | 24 | my $RESET = "\033[0m"; 25 | 26 | 27 | sub get_file_type { 28 | return if (length($_) < 10); 29 | 30 | if (substr($_, 0, 1) eq 'l') { 31 | return 'ln'; 32 | } elsif (substr($_, 0, 1) eq 'd') { 33 | return 'di'; 34 | } elsif (substr($_, 0, 1) eq 's') { 35 | return 'so'; 36 | } elsif (substr($_, 3, 1) eq 'S') { 37 | return 'su'; 38 | } elsif (substr($_, 6, 1) eq 'S') { 39 | return 'sg'; 40 | } elsif (substr($_, 0, 1) eq 'p') { 41 | return 'pi'; 42 | } elsif (substr($_, 0, 1) eq 'c') { 43 | return 'cd'; 44 | } elsif (substr($_, 0, 1) eq 'b') { 45 | return 'bd'; 46 | } elsif (substr($_, 0, 1) eq 'D') { 47 | return 'do'; 48 | } elsif (substr($_, 3, 1) eq 'x') { 49 | return 'ex'; 50 | } elsif (substr($_, -3, 2) =~ /.\//) { 51 | return 'di'; 52 | } elsif (/\.\w{1,3}$/) { 53 | return '*' . $&; 54 | } 55 | } 56 | 57 | sub get_filename { 58 | my $suntar_date = m{ 59 | (?: [A-Z]\w\w) # Month 60 | \s+ 61 | \d{1,2} # Day 62 | \s+ 63 | \d{2}:\d{2} # Time 64 | \s+ 65 | \d{4} # Year 66 | [\s,]+ 67 | (.+?) # Capture group 1: filename 68 | (?=\s->|$) 69 | }gx; 70 | 71 | if ($suntar_date) { 72 | return $1, pos(); 73 | } 74 | 75 | my $bsdtar_date = m{ 76 | (?: [A-Z]\w\w) # Month 77 | \s+ 78 | \d{1,2} # Day 79 | \s+ 80 | (?: (?: \d{4}) | (?: \d{2}:\d{2})) # Year or time 81 | \s 82 | (.+?) # Capture group 1: filename 83 | (?=\s->|$) 84 | }gx; 85 | 86 | if ($bsdtar_date) { 87 | return $1, pos(); 88 | } 89 | 90 | my $bsdtar_date_dmy = m{ 91 | \d{1,2} # Day 92 | \s+ 93 | (?: [A-Z]\w\w) # Month 94 | \s+ 95 | (?: (?: \d{4}) | (?: \d{2}:\d{2})) # Year or time 96 | \s+ 97 | (.+?) # Capture group 1: filename 98 | (?=\s->|$) 99 | }gx; 100 | 101 | if ($bsdtar_date_dmy) { 102 | return $1, pos(); 103 | } 104 | 105 | my $gnutar_date = m{ 106 | \d{4}-\d{2}-\d{2} # Date (%Y-%m-%d) 107 | \s 108 | \d{2}:\d{2} # Time (%H:%M) 109 | (?: :\d{2})? # [Optional] seconds in time 110 | \s+ 111 | (.+?) # Capture group 1: filename 112 | (?=\s->|$) 113 | }gx; 114 | 115 | if ($gnutar_date) { 116 | return $1, pos(); 117 | } 118 | } 119 | 120 | sub color_filename { 121 | my ($color) = @_; 122 | 123 | my ($filename, $pos) = get_filename(); 124 | 125 | if ($filename && $pos) { 126 | substr($_, $pos - length($filename), length($filename)) = $color . $filename . $RESET; 127 | } 128 | } 129 | 130 | sub default_ls_colors { 131 | return ''; 132 | } 133 | 134 | if ( -t STDIN ) { 135 | print "Example: tar tvzf some_tarball.tar.gz | archive_color\n"; 136 | exit(0); 137 | } 138 | 139 | 140 | my %FILE_TYPE_TO_COLOR = ( 141 | "di" => "\033[01;34m", 142 | "ln" => "\033[01;36m", 143 | "ex" => "\033[01;32m", 144 | "so" => "\033[01;35m", 145 | "pi" => "\033[40;33m", 146 | "bd" => "\033[40;33;01m", 147 | "cd" => "\033[40;33;01m", 148 | "su" => "\033[37;41m", 149 | "sg" => "\033[30;43m", 150 | ); 151 | 152 | my $tar_colors = $ENV{'TAR_COLORS'} || $ENV{'LS_COLORS'} || default_ls_colors(); 153 | 154 | foreach (split(':', $tar_colors)) { 155 | my ($type, $codes) = split('='); 156 | $FILE_TYPE_TO_COLOR{$type} = "\033[" . $codes . "m"; 157 | } 158 | 159 | while (<>) { 160 | my $type = get_file_type(); 161 | 162 | if ($type && $FILE_TYPE_TO_COLOR{$type}) { 163 | color_filename($FILE_TYPE_TO_COLOR{$type}); 164 | } 165 | 166 | print; 167 | } 168 | 169 | 170 | # ABSTRACT: colors output of `tar tvf` 171 | # PODNAME: archive_color 172 | 173 | =pod 174 | 175 | =head1 SYNOPSIS 176 | 177 | tar tvzf | archive_color 178 | 179 | =head1 DESCRIPTION 180 | 181 | Tarcolor colors the output of `tar tvf` similarly to how ls does it. 182 | 183 | Colors output of `tar tvf` similarly to the way GNU ls (in GNU coreutils) would 184 | color a directory listing. 185 | 186 | Colors can be customized using an environment variable: 187 | 188 | TAR_COLORS='di=01;34:ln=01;36:ex=01;32:so=01;40:pi=01;40:bd=40;33:cd=40;33:su=0;41:sg=0;46' 189 | 190 | The format for TAR_COLORS is similar to the format used by LS_COLORS Check out 191 | the online LSCOLORS generator at http://geoff.greer.fm/lscolors/ 192 | 193 | =head1 SEE ALSO 194 | 195 | tarcolorauto(1) 196 | 197 | =head1 SOURCE CODE 198 | 199 | https://github.com/msabramo/tarcolor 200 | 201 | =cut 202 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # the above line will use any perl that is in your PATH. You might want to 3 | # replace it by #!/usr/bin/perl or similar if this does not work for you. 4 | 5 | use strict; 6 | use warnings; 7 | use File::Copy; 8 | use Getopt::Long; 9 | 10 | # find sxw2txt and other scripts in current dir, if scripts not installed yet 11 | $ENV{PATH} .= ':.'; 12 | 13 | use vars qw($opt_help $opt_prefix $opt_nomake $opt_shell $opt_all_completions); 14 | 15 | Getopt::Long::Configure("prefix_pattern=--"); 16 | my $result = GetOptions('help+', 'prefix=s', 'shell=s', 'nomake+', 'all-completions+'); 17 | if ( $ARGV[0] or ! $result or $opt_help) { 18 | print << 'EOF'; 19 | Usage: configure [options] 20 | Options: 21 | --help print this message 22 | --shell= specify an alternative shell path (zsh/bash) to use 23 | --nomake do not generate a Makefile 24 | --all-completions always install all completions 25 | Directory and file names: 26 | --prefix=PREFIX install lesspipe.sh in PREFIX/bin (/usr/local) 27 | 28 | configure generates by default a Makefile 29 | EOF 30 | exit ! $opt_help ? 1 : 0; 31 | } 32 | 33 | my $bash_complete_dir = `pkg-config --variable=completionsdir bash-completion`; 34 | chomp $bash_complete_dir; 35 | my $prefix = $opt_prefix || $ENV{PREFIX} || '/usr/local'; 36 | 37 | # remove trailing slash and trailing bin directory 38 | print "removed trailing /bin dir from prefix\n" if $prefix =~ m|/bin/?$|; 39 | $prefix =~ s|(/bin)?/?$||; 40 | if ( $opt_shell and -f $opt_shell and $opt_shell =~ /^\// ) { 41 | # do nothing 42 | } elsif ( $opt_shell) { 43 | print "unuseable shell $opt_shell: not executable or no absolute path\n"; 44 | exit 1; 45 | } 46 | 47 | # gererate Makefile 48 | my @bad = (); 49 | my $shell = check_shell_vers(); 50 | if ( ! $opt_nomake ) { 51 | my $no_bash = (grep {/bash/} @bad and ! $opt_all_completions); 52 | my $no_zsh = (grep {/zsh/} @bad and ! $opt_all_completions); 53 | open OUT, ">Makefile"; 54 | while () { 55 | next if /bash_complete_dir/ and $no_bash; 56 | next if /zsh\/site-functions/ and $no_zsh; 57 | s/opt_prefix/$prefix/; 58 | if ($bash_complete_dir and ! $opt_prefix) { 59 | s/mkdir.*bash-completion.*/mkdir -p $bash_complete_dir/; 60 | s/cp.*bash-completion.*/cp .\/less_completion $bash_complete_dir/; 61 | } 62 | print OUT; 63 | } 64 | close OUT; 65 | } 66 | $bash_complete_dir = "$prefix/share/bash-completion"; 67 | print "installing bash completion in $bash_complete_dir\nIn bash, please preload the completion, dynamic invocation does not work\n. $bash_complete_dir/less_completion\nOr consider installing the file less_completion in /etc/bashcompletion.d\n"; 68 | 69 | open F, "lesspipe.sh"; 70 | { 71 | my $line1 = ; 72 | exit if $line1 =~ /^$shell$/; 73 | local $/; 74 | my $in = ; 75 | print "generating new lesspipe.sh using '$shell' as first line\n"; 76 | copy "lesspipe.sh", "lesspipe.sh.old"; 77 | open OUT, ">lesspipe.sh"; 78 | print OUT "$shell\n"; 79 | print OUT $in; 80 | close OUT; 81 | } 82 | exit 0; 83 | 84 | sub check_shell_vers { 85 | # define useable shells, the order is important ! 86 | my @shells = qw( /bin/bash /bin/zsh /bin/sh); 87 | @shells = ( $opt_shell ) if $opt_shell; 88 | my $selected_shell = ''; 89 | my $shellcmd = ''; 90 | for my $shell ( @shells ) { 91 | # get the basename of the shell and shell options 92 | my ($path, $name, $opt); 93 | if ( $shell =~ /(.*)\/([^\/]+)(\s.*)$/ ) { 94 | ($path, $name, $opt) = ($1, $2, $3); 95 | } else { 96 | ($path, $name, $opt) = ($1, $2, "") if $shell =~ /(.*)\/([^\/]+)\s*$/; 97 | } 98 | # do we have the shell in the PATH 99 | my $versstr = uc $name.'_VERSION'; 100 | $versstr = 'BASH_VERSION' if $name eq 'sh'; 101 | my @where = grep { -x $_."/$name" } split ':', $ENV{PATH}; 102 | $where[0] = $path if -x $path.'/'.$name; 103 | my $file = $where[0].'/'.$name if $where[0]; 104 | if ( ! $where[0] or ! -x $file ) { 105 | print "$name not found in the PATH, continuing\n"; 106 | push @bad, $shell; 107 | next; 108 | } 109 | # get the shell version 110 | my $v = `$file -c \'echo \$$versstr\'`; 111 | chomp $v; 112 | ### print "$file $v found in the PATH\n"; 113 | if ( $name eq 'bash' or $name eq 'sh' ) { 114 | my $tst = `$file -c \'if [[ -n 1 && -n 2 ]];then true;fi 2>&1\'`; 115 | if ( $tst ) { 116 | push @bad, $shell; 117 | next; 118 | } 119 | } 120 | $selected_shell = $name if ! $selected_shell; 121 | $shellcmd = "$file$opt" if ! $shellcmd; 122 | } 123 | if ( !$selected_shell ) { 124 | print "Sorry, no useable shell found, the following were tried:\n@bad\n"; 125 | print "You could run configure --shell= to retry\n"; 126 | exit 1; 127 | } 128 | return "#!/usr/bin/env $selected_shell"; 129 | } 130 | __END__ 131 | # This is a generated file, do not edit it. Use configure to modify it 132 | PREFIX = opt_prefix 133 | 134 | .PHONY: install 135 | 136 | all: 137 | ./configure --prefix=$(PREFIX) 138 | test: 139 | ./test.pl 140 | install: 141 | mkdir -p $(DESTDIR)$(PREFIX)/bin 142 | mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1 143 | mkdir -p $(DESTDIR)$(PREFIX)/share/zsh/site-functions 144 | mkdir -p $(DESTDIR)$(PREFIX)/share/bash-completion/ 145 | cp ./code2color ./sxw2txt ./archive_color ./lesspipe.sh ./vimcolor ./lesscomplete $(DESTDIR)$(PREFIX)/bin 146 | cp ./lesspipe.1 $(DESTDIR)$(PREFIX)/share/man/man1 147 | cp ./less_completion $(DESTDIR)$(PREFIX)/share/bash-completion/ 148 | cp ./_less $(DESTDIR)$(PREFIX)/share/zsh/site-functions 149 | chmod 0755 $(DESTDIR)$(PREFIX)/bin/lesspipe.sh 150 | chmod 0755 $(DESTDIR)$(PREFIX)/bin/sxw2txt 151 | chmod 0755 $(DESTDIR)$(PREFIX)/bin/code2color 152 | chmod 0755 $(DESTDIR)$(PREFIX)/bin/archive_color 153 | chmod 0755 $(DESTDIR)$(PREFIX)/bin/vimcolor 154 | chmod 0755 $(DESTDIR)$(PREFIX)/bin/lesscomplete 155 | clean: 156 | mv Makefile Makefile.old 157 | -------------------------------------------------------------------------------- /german.txt: -------------------------------------------------------------------------------- 1 | Artikel in Linux-Magazin 1/2001 Seiten 172-174 2 | ============================================== 3 | (nur die englische Übersetzung english.txt wird von mir aktualisiert) 4 | 5 | Mehr betrachten mit less 6 | 7 | Wer kennt nicht das Problem: Soeben wurde ein File aus dem Internet 8 | heruntergeladen, das ein vielversprechendes Programm enthalten soll. Doch 9 | bevor man an das Auspacken und Installieren geht, möchte man vielleicht 10 | erst einmal das README lesen oder eine zugehörige Manpage betrachten. Doch 11 | wie war das gleich mit dem Extrahieren von Files? Das tar Kommando hat man 12 | ja meist im Griff, zip Dateien klappen auch noch mit Nachdenken. Doch wie 13 | in aller Welt bekomme ich ein einzelnes File aus einer RPM-Datei? Richtig, 14 | da gab es ja noch den Midnight Commander und ähnliche Programme, zumindest 15 | wenn man unter Linux arbeitet. Doch was macht man auf anderen UNIX-Plattformen? 16 | In der Regel ist dort nichts Vergleichbares installiert. Und wie betrachtet 17 | man eine Manpage, die nicht in MANPATH enthalten ist? Für all diese Fragen 18 | gibt es natürlich Lösungen, aber in vielen Fällen sind UNIX-Benutzer bei 19 | solchen Problemen überfordert. Unter UNIX fällt einem als Filebetrachter 20 | sofort less [1], die bessere Alternative zu more, ein. Und da less in Gestalt 21 | der Umgebungsvariablen LESSOPEN durch externe Filter erweitert werden kann, 22 | ist less als extrem flexibler Browser konfigurierbar. Einige 23 | Linux-Distributionen legen bereits ein Skript lesspipe.sh bei, das zur 24 | Leistungssteigerung von less beiträgt. 25 | 26 | In diesem Artikel soll ein Inputfilter für less vorgestellt werden, der 27 | eine Vielzahl gängiger Fileformate beherrscht und relativ leicht 28 | erweiterbar ist. Als Skriptsprache wurde eine Korn-shell kompatible 29 | Sprache (ksh, bash, zsh) gewählt, da das Vorhandensein der Shell 30 | immer gewährleistet ist und in den meisten Fällen vergleichsweise wenige 31 | Systemressourcen in Anspruch genommen werden. Ansonsten wäre die 32 | Realisierung des gleichen Konzeptes etwa in perl an vielen Stellen 33 | einfacher. 34 | 35 | Dem vorzustellenden Skript lesspipe.sh liegen zwei Ideen zugrunde. Die 36 | Erkennung des Fileformats erfolgt nicht über die Fileendung, diese 37 | Methode aus der DOS-Welt ist fehleranfällig und hat einen hohen 38 | Pflegeaufwand. Für die Fileformaterkennung unter UNIX ist der Befehl file 39 | gut geeignet, aktualisierte Formatbeschreibungen sind zudem jederzeit in 40 | der aktuellen file Version zu finden [2]. Die zweite Idee besteht darin, dem 41 | lesspipe eine Hierarchie von Filenamen übergeben zu können, um auch 42 | einzelne Files aus Archiven zu betrachten. Da lesspipe.sh aber nur ein 43 | Argument übergeben werden kann, wird die hierarchische Liste von Filenamen 44 | durch ein spezielles Trennzeichen verbunden. Als Zeichen wurde der Doppelpunkt 45 | gewählt, welcher selten genug in Filenamen vorkommt. Auf jeder Stufe des 46 | File-Extrahierens wird der Filetyp bestimmt. Damit wird garantiert, daß auch 47 | auf dem untersten Niveau noch eine zum Filetyp passende Anzeige erfolgt. 48 | 49 | Das Betrachten einer man-Page in einem tar Archiv, welches seinerseits in 50 | einem RPM Paket steckt, könnte in folgenden Schritten erfolgen: 51 | 52 | less file-3.27-43.i386.spm 53 | 54 | gibt folgenden Output: 55 | ... 56 | SuSE series: a 57 | -rw-r--r-- 1 root root 12953 Feb 3 11:45 file-3.27.dif 58 | -rw-r--r-- 1 root root 123541 Jul 6 1999 file-3.27.tar.gz 59 | -rw-r--r-- 1 root root 3398 Mar 25 07:31 file.spec 60 | 61 | less file-3.27-43.i386.spm:file-3.27.tar.gz 62 | 63 | liefert u.a.: 64 | ... 65 | -rw-rw-r-- christos/christos 8740 1999-02-14 18:16 file-3.27/file.c 66 | -rw-rw-r-- christos/christos 4886 1999-02-14 18:16 file-3.27/file.h 67 | -rw-rw-r-- christos/christos 13428 1999-02-14 18:16 file-3.27/file.man 68 | ... 69 | 70 | less file-3.27-43.i386.spm:file-3.27.tar.gz:file-3.27/file.man 71 | 72 | liefert dann das gewünschte Resultat, die Anzeige von file.man als Manpage. 73 | Will man stattdessen die nroff Quelle sehen, hängt man einen Doppelpunkt an. 74 | 75 | less file-3.27-43.i386.spm:file-3.27.tar.gz:file-3.27/file.man: 76 | 77 | Auch das Extrahieren einzelner Files aus dem Archiv ist möglich, etwa mit 78 | 79 | less file-3.27-43.i386.spm:file-3.27.tar.gz:file-3.27/file.c > file.c 80 | 81 | Wollte man stattdessen file.man in ein File umlenken, macht es sicher mehr 82 | Sinn, die nroff Quelle als den formatierten Output abzuspeichern. Auch in 83 | diesem Fall sollte also ein Doppelpunkt angehängt werden. 84 | 85 | Wenn man mit dem tar File file-3.27.tar.gz gleichermaßen verfährt, erhält man 86 | ein unkomprimiertes tar File, da eine eventuell mögliche Dekompression immer 87 | erfolgt. Die Dekomprimierung ist im Normalfall auch wünschenswert. Wenn man 88 | aber zum Beispiel das im RPM enthaltene File file-3.27.tar.gz extrahieren 89 | will, stört die Dekompression. Durch Anfügen eines weiteren Doppelpunktes 90 | bleibt auch die komprimierte Datei intakt, indem man z.B. schreibt: 91 | 92 | less file-3.27-43.i386.spm:file-3.27.tar.gz:: > file-3.27.tar.gz 93 | 94 | Das (etwas vereinfachte) Skript lesspipe.sh ist in Listing 1 abgedruckt, 95 | eine aktuelle und komplette Version, die bis zur Rekursionstiefe 6 geht 96 | und weitere Filetypen berücksichtigt, ist unter [3] zu finden. 97 | 98 | In einigen wenigen Fällen erkennt der File-Befehl den falschen Typ 99 | (insbesondere nroff-Format), dann kann man eine ungefilterte Ansicht durch 100 | nachgestellten Doppelpunkt bei der Fileangabe erzwingen. Dann erfolgt die 101 | Ausgabe des Files mit dem Befehl cat. 102 | 103 | Eine Erweiterung des Skripts um zusätzliche Fileformate ist relativ leicht 104 | möglich, Im einfachsten Fall hat man für neue Formate in die Funktion isfinal 105 | nach dem Muster aus Zeilen 125-127 Befehle einzufügen, die auf STDOUT 106 | schreiben. Handelt es sich um ein neues Kompressionsformat, wird man in der 107 | Funktion get_cmd neue Befehle analog zu den Zeilen 64-65 einfügen müssen. 108 | 109 | Etwas aufwendiger wird es mit einem neuen Archivformat. Das Anzeigen des 110 | Archivinhaltes geschieht in der Funktion isfinal (siehe z.B. Zeilen 118-120), 111 | während die Extraktion von Files aus dem Archiv in get_cmd nach dem Muster 112 | in Zeilen 74-75 festzulegen ist. 113 | 114 | Damit wäre eigentlich die Aufgabe erledigt, beliebige Fileformate 115 | zu erkennen und Informationen aus diesen Files gefiltert anzuzeigen. Wie 116 | man sowohl an der Benutzung von lesspipe.sh als auch an der häufigen 117 | Benutzung von Pipes in der Funktion show sieht, müssen die Filter von 118 | STDIN lesen und auf STDOUT schreiben können. Da einige Programme das aber 119 | von Haus aus nicht können, muß ein Umweg über temporäre Files gemacht 120 | werden. Wo das erforderlich war, wurden neue Funktionen definiert. Sollte 121 | ein solcher Fall neu implementiert werden müssen, kann man sich an der 122 | Funktion iszip (Zeilen 84-91) oder isrpm (Zeilen 93-103) orientieren. 123 | 124 | Die Funktion lesspipe.sh kann man z.B. nach /usr/local/bin kopieren. Um 125 | die damit erweiterte Funktionalität von less nutzen zu können, setzt man 126 | in sh-ähnlichen Shells (sh, ksh, zsh, bash) 127 | 128 | LESSOPEN ="|/usr/local/bin/lesspipe.sh %s"; export LESSOPEN 129 | 130 | In der csh und tcsh schreibt man stattdessen 131 | 132 | setenv LESSOPEN "|/usr/local/bin/lesspipe.sh %s" 133 | 134 | Dieses Skript lesspipe.sh als Erweiterung von less läuft beim Deutschen 135 | Elektronensynchrotron seit mehr als 3 Jahren auf einer Vielzahl 136 | verschiedener UNIX-Plattformen. 137 | 138 | [1] http://www.greenwoodsoftware.com/less/ 139 | [2] ftp://ftp.astron.com/pub/file 140 | [3] http://www.ifh.de/~friebel/unix/lesspipe.html 141 | 142 | Listing 1 143 | ========= 144 | 145 | 1 #!/bin/bash 146 | 2 #================================================================== 147 | 3 # lesspipe.sh, a preprocessor for less 148 | 4 # Author: Wolfgang Friebel, DESY 149 | 5 #================================================================== 150 | 6 tarcmd=gtar; 151 | 7 filecmd='file -L'; # file (recommended file-3.27 or better) 152 | 8 sep=: # file name separator 153 | 9 altsep== # alternate separator character 154 | 10 if [[ -f "$1" && "$1" = *$sep* || "$1" = *$altsep ]]; then 155 | 11 sep=$altsep 156 | 12 fi 157 | 13 tmp=/tmp/.lesspipe.$$ # temp file name 158 | 14 trap 'rm -f $tmp $tmp.fin $tmp. $tmp.. $tmp.1' 0 159 | 15 trap PIPE 160 | 16 161 | 17 show () { 162 | 18 file1=${1%%$sep*} 163 | 19 rest1=${1#$file1} 164 | 20 rest11=${rest1#$sep} 165 | 21 file2=${rest11%%$sep*} 166 | 22 rest2=${rest11#$file2} 167 | 23 rest11=$rest1 168 | 24 if [[ $# = 1 ]]; then 169 | 25 type=`$filecmd "$file1" | cut -d : -f 2-` 170 | 26 get_cmd "$type" "$file1" $rest1 171 | 27 if [[ "$cmd" != "" ]]; then 172 | 28 show "-$rest1" "$cmd" 173 | 29 else 174 | 30 isfinal "$type" "$file1" $rest11 175 | 31 fi 176 | 32 elif [[ $# = 2 ]]; then 177 | 33 type=`$2 | $filecmd - | cut -d : -f 2-` 178 | 34 get_cmd "$type" "$file1" $rest1 179 | 35 if [[ "$cmd" != "" ]]; then 180 | 36 show "-$rest1" "$2" "$cmd" 181 | 37 else 182 | 38 $2 | isfinal "$type" - $rest11 183 | 39 fi 184 | 40 elif [[ $# = 3 ]]; then 185 | 41 type=`$2 | $3 | $filecmd - | cut -d : -f 2-` 186 | 42 get_cmd "$type" "$file1" $rest1 187 | 43 if [[ "$cmd" != "" ]]; then 188 | 44 show "-$rest1" "$2" "$3" "$cmd" 189 | 45 else 190 | 46 $2 | $3 | isfinal "$type" - $rest11 191 | 47 fi 192 | 48 elif [[ $# = 4 ]]; then 193 | 49 type=`$2 | $3 | $4 | $filecmd - | cut -d : -f 2-` 194 | 50 get_cmd "$type" "$file1" $rest1 195 | 51 if [[ "$cmd" != "" ]]; then 196 | 52 echo "$0: Too many levels of encapsulation" 197 | 53 else 198 | 54 $2 | $3 | $4 | isfinal "$type" - $rest11 199 | 55 fi 200 | 56 fi 201 | 57 } 202 | 58 203 | 59 get_cmd () { 204 | 60 cmd= 205 | 61 if [[ "$1" = *bzip* || "$1" = *compress* ]]; then 206 | 62 if [[ "$3" = $sep$sep ]]; then 207 | 63 return 208 | 64 elif [[ "$1" = *bzip* ]]; then 209 | 65 cmd="bzip2 -c -d $2" 210 | 66 else 211 | 67 cmd="gzip -c -d $2" 212 | 68 fi 213 | 69 return 214 | 70 fi 215 | 71 216 | 72 rest1=$rest2 217 | 73 if [[ "$file2" != "" ]]; then 218 | 74 if [[ "$1" = *tar* ]]; then 219 | 75 cmd="$tarcmd Oxf $2 $file2" 220 | 76 elif [[ "$1" = *RPM* ]]; then 221 | 77 cmd="isrpm $2 $file2" 222 | 78 elif [[ "$1" = *Zip* ]]; then 223 | 79 cmd="iszip $2 $file2" 224 | 80 fi 225 | 81 fi 226 | 82 } 227 | 83 228 | 84 iszip () { 229 | 85 if [[ "$1" = - ]]; then 230 | 86 rm -f $tmp 231 | 87 cat > $tmp 232 | 88 set $tmp "$2" 233 | 89 fi 234 | 90 unzip -avp "$1" "$2" 235 | 91 } 236 | 92 237 | 93 isrpm () { 238 | 94 if [[ "$1" = - ]]; then 239 | 95 rm -f $tmp 240 | 96 cat > $tmp 241 | 97 set $tmp "$2" 242 | 98 fi 243 | 99 echo $tmp.1 > $tmp. 244 | 100 rm -f $tmp.1 245 | 101 rpm2cpio $1|cpio -i --quiet --rename-batch-file $tmp. ${2##/} 246 | 102 cat $tmp.1 247 | 103 } 248 | 104 249 | 105 isfinal() { 250 | 106 if [[ "$3" = $sep || "$3" = $sep$sep ]]; then 251 | 107 cat $2 252 | 108 return 253 | 109 elif [[ "$2" = - && ( "$1" = *RPM* || "$1" = *Zip* ) ]]; then 254 | 110 cat > $tmp.fin 255 | 111 set "$1" $tmp.fin 256 | 112 fi 257 | 113 if [[ "$1" = *No\ such* ]]; then 258 | 114 return 259 | 115 elif [[ "$1" = *directory* ]]; then 260 | 116 echo "==> This is a directory, showing the output of ls -lAL" 261 | 117 ls -lAL "$2" 262 | 118 elif [[ "$1" = *tar* ]]; then 263 | 119 echo "==> use tar_file${sep}contained_file to view a file in the archive" 264 | 120 $tarcmd tvf "$2" 265 | 121 elif [[ "$1" = *RPM* ]]; then 266 | 122 echo "==> use RPM_file${sep}contained_file to view a file in the RPM" 267 | 123 rpm -p "$2" -qiv 268 | 124 rpm2cpio $2|cpio -i -tv --quiet 269 | 125 elif [[ "$1" = *roff* ]]; then 270 | 126 echo "==> append $sep to filename to view the nroff source" 271 | 127 groff -s -p -t -e -Tascii -mandoc ${2#-} 272 | 128 elif [[ "$1" = *executable* ]]; then 273 | 129 echo "==> append $sep to filename to view the binary file" 274 | 130 strings ${2#-} 275 | 131 elif [[ "$1" = *Zip* ]]; then 276 | 132 echo "==> use zip_file${sep}contained_file to view a file in the archive" 277 | 133 unzip -lv "$2" 278 | 134 elif [[ "$1" = *HTML* ]]; then 279 | 135 echo "==> append $sep to filename to view the HTML source" 280 | 136 LESSOPEN= 281 | 137 w3m -dump -X -T text/html "$2" 282 | 138 elif [[ "$2" = - ]]; then 283 | 139 cat 284 | 140 fi 285 | 141 } 286 | 142 287 | 143 show "$a" 288 | -------------------------------------------------------------------------------- /less_completion: -------------------------------------------------------------------------------- 1 | # bash completion for less 2 | 3 | _list_archive() 4 | { 5 | local cur prev words cword 6 | if declare -F _init_completions >/dev/null 2>&1; then 7 | _init_completion 8 | else 9 | COMPREPLY=() 10 | _get_comp_words_by_ref cur prev words cword 11 | fi 12 | 13 | local fn 14 | 15 | set -- "${words[@]}" 16 | if [[ "${words[@]}" == *[:=]* && $COMP_LINE != *\ ]]; then 17 | local i=${#words[*]} 18 | fn="${words[$i]}" 19 | while ((i-- > 2)); do 20 | [[ "${words[$i]}" == : ]] && fn="${words[$i-1]}:$fn" 21 | done 22 | fn="${fn%[:=]*}" 23 | cur=${cur#$fn[:=]} 24 | cur=${cur#:} 25 | local IFS=$'\n' 26 | COMPREPLY=($(compgen -o filenames -W "$( 27 | lesscomplete "$fn"| 28 | while read line; do 29 | printf "%q\n" "$(printf %q"\n" "$line")" 30 | done 31 | )" -- "$cur")) 32 | return 0 33 | else 34 | _filedir 35 | fi 36 | } 37 | 38 | if [[ ${COMP_LESS_INTERNAL_PATHS-} ]]; then 39 | complete -F _list_archive -o dirnames less 40 | else 41 | complete -F _list_archive less 42 | fi 43 | 44 | # ex: filetype=sh 45 | -------------------------------------------------------------------------------- /lesscomplete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # lesscomplete, a helper script for the _less completion script 3 | # synced with lesspipe 2.18 4 | # Author: Wolfgang Friebel (wp.friebel AT gmail.com) 5 | 6 | has_cmd () { 7 | command -v "$1" > /dev/null 8 | } 9 | 10 | fileext () { 11 | fn=${1##*/} 12 | case "$fn" in 13 | .*.*) extension=${fn##*.} ;; 14 | .*) extension=${fn#.} ;; 15 | *.*) extension=${fn##*.} ;; 16 | esac 17 | echo "$extension" 18 | } 19 | 20 | filetype () { 21 | # do not depend on the file extension, if possible 22 | fname="$1" 23 | if [[ "$1" == - || -z $1 ]]; then 24 | declare t 25 | t=$(nexttmp) 26 | head -c 1000000 > "$t" 2>/dev/null 27 | [[ -z $fileext ]] && fname="$t" || fname="$fileext" 28 | set "$t" "$2" 29 | fi 30 | fext=$(fileext "$fname") 31 | ### get file type from mime type 32 | declare ft 33 | ft=$(file -L -s -b --mime "$1" 2> /dev/null) 34 | [[ $ft == *=* ]] && fchar="${ft##*=}" || fchar=utf-8 35 | fcat="${ft%/*}" 36 | ft="${ft#*/}"; ft="${ft%;*}"; ft="${ft#x-script.}"; ft="${ft#x-}" 37 | ftype="${ft#vnd\.}" 38 | ### get file type from 'file' command for an unspecific result 39 | if [[ "$fcat" == application ]]; then 40 | if [[ "$ftype" == octet-stream || "$fcat" == text && $ftype == plain ]]; then 41 | ft=$(file -L -s -b "$1" 2> /dev/null) 42 | if [[ $ft == data ]]; then 43 | case "$fext" in 44 | br|bro|tbr) 45 | ftype=brotli ;; 46 | lz4|lt4|tz4|tlz4) 47 | ftype=lz4 ;; 48 | esac 49 | fi 50 | fi 51 | if [[ "$fext" == appimage || "$fext" == snap ]]; then 52 | ftype="$fext" 53 | elif [[ "$fext" == AppImage ]]; then 54 | ftype=appimage 55 | fi 56 | fi 57 | 58 | echo "$ftype:$fchar:$fcat" 59 | } 60 | 61 | nexttmp () { 62 | declare new="$tmpdir/lesspipe.$RANDOM.${ft%%:*}" 63 | echo "$new" 64 | } 65 | 66 | istemp () { 67 | prog="$1" 68 | shift 69 | if [[ "$1" == - ]]; then 70 | shift 71 | t=$(nexttmp) 72 | cat > "$t" 73 | $prog "$t" "$@" 74 | else 75 | $prog "$@" 76 | fi 77 | } 78 | 79 | show () { 80 | file1="${1%%"$sep"*}" 81 | rest1="${1#"$file1"}" 82 | while [[ "$rest1" == "$sep$sep"* ]]; do 83 | [[ "$rest1" == "$sep$sep" ]] && break 84 | rest1="${rest1#"$sep$sep"}" 85 | file1="${rest1%%"$sep"*}" 86 | rest1="${rest1#"$file1"}" 87 | file1="${1%"$rest1"}" 88 | done 89 | [[ ! -e "$file1" && "$file1" != '-' ]] && exit 1 90 | rest11="${rest1#"$sep"}" 91 | file2="${rest11%%"$sep"*}" 92 | rest2="${rest11#"$file2"}" 93 | while [[ "$rest2" == "$sep$sep"* ]]; do 94 | [[ "$rest2" == "$sep$sep" ]] && break 95 | rest2="${rest2#"$sep$sep"}" 96 | file2="${rest2%%"$sep"*}" 97 | rest2="${rest2#"$file2"}" 98 | file2="${rest11%"$rest2"}" 99 | done 100 | rest2="${rest11#"$file2"}" 101 | rest11="$rest1" 102 | 103 | if [[ "${cmd[*]}" == "" ]]; then 104 | ft=$(filetype "$file1") 105 | get_unpack_cmd "$ft" "$file1" "$rest1" 106 | if [[ "${cmd[*]}" != "" && -z $colorizer ]]; then 107 | show "-$rest1" 108 | else 109 | # if nothing to convert, exit without a command 110 | 111 | isfinal "$ft" "$file1" "$rest11" 112 | fi 113 | elif [[ "$c1" == "" ]]; then 114 | c1=("${cmd[@]}") 115 | ft=$("${c1[@]}" | filetype -) || exit 1 116 | get_unpack_cmd "$ft" "$file1" "$rest1" 117 | if [[ "${cmd[*]}" != "" && -z $colorizer ]]; then 118 | show "-$rest1" 119 | else 120 | "${c1[@]}" | isfinal "$ft" - "$rest11" 121 | fi 122 | elif [[ "$c2" == "" ]]; then 123 | c2=("${cmd[@]}") 124 | ft=$("${c1[@]}" | "${c2[@]}" | filetype -) || exit 1 125 | get_unpack_cmd "$ft" "$file1" "$rest1" 126 | if [[ "${cmd[*]}" != "" && -z $colorizer ]]; then 127 | show "-$rest1" 128 | else 129 | "${c1[@]}" | "${c2[@]}" | isfinal "$ft" - "$rest11" 130 | fi 131 | elif [[ "$c3" == "" ]]; then 132 | c3=("${cmd[@]}") 133 | ft=$("${c1[@]}" | "${c2[@]}" | "${c3[@]}" | filetype -) || exit 1 134 | get_unpack_cmd "$ft" "$file1" "$rest1" 135 | if [[ "${cmd[*]}" != "" && -z $colorizer ]]; then 136 | show "-$rest1" 137 | else 138 | "${c1[@]}" | "${c2[@]}" | "${c3[@]}" | isfinal "$ft" - "$rest11" 139 | fi 140 | elif [[ "$c4" == "" ]]; then 141 | c4=("${cmd[@]}") 142 | ft=$("${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | filetype -) || exit 1 143 | get_unpack_cmd "$ft" "$file1" "$rest1" 144 | if [[ "${cmd[*]}" != "" && -z $colorizer ]]; then 145 | show "-$rest1" 146 | else 147 | "${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | isfinal "$ft" - "$rest11" 148 | fi 149 | elif [[ "$c5" == "" ]]; then 150 | c5=("${cmd[@]}") 151 | ft=$("${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | "${c5[@]}" | filetype -) || exit 1 152 | get_unpack_cmd "$ft" "$file1" "$rest1" 153 | if [[ "${cmd[*]}" != "" && -z $colorizer ]]; then 154 | echo "$0: Too many levels of encapsulation" 155 | else 156 | "${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | "${c5[@]}" | isfinal "$ft" - "$rest11" 157 | fi 158 | fi 159 | } 160 | 161 | get_unpack_cmd () { 162 | fchar="${1%:*}"; fchar="${fchar#*:}" 163 | fcat="${1##*:}" 164 | x="${1%%:*}" 165 | cmd=() 166 | [[ "$3" == $sep$sep ]] && return 167 | declare t 168 | # uncompress 169 | case $x in 170 | gzip|bzip2|lzip|lzma|xz|brotli|compress) 171 | # remember name of uncompressed file 172 | [[ $2 == - ]] || fileext="$2" 173 | fileext=${fileext%%.gz}; fileext=${fileext%%.bz2} 174 | [[ $x == compress ]] && x=gzip 175 | has_cmd "$x" && cmd=("$x" -cd "$2") ;; 176 | zstd) 177 | has_cmd zstd && cmd=(zstd -cdqM1073741824 "$2") ;; 178 | lz4) 179 | has_cmd lz4 && cmd=(lz4 -cdq "$2") ;; 180 | esac 181 | [[ ${cmd[*]} == '' ]] || return 182 | [[ "$3" == "$sep" ]] && return 183 | file2=${3#"$sep"} 184 | file2=${file2%%"$sep"*} 185 | # remember name of file to extract or file type 186 | [[ -n "$file2" ]] && fileext="$file2" 187 | # extract from archive 188 | rest1="$rest2" 189 | rest2= 190 | prog= 191 | case "$x" in 192 | tar) 193 | prog=tar 194 | has_cmd bsdtar && prog=bsdtar ;; 195 | rpm) 196 | { has_cmd cpio && has_cmd rpm2cpio; } || 197 | { has_cmd bsdtar; } && cmd=(isrpm "$2" "$file2") ;; 198 | java-archive|zip) 199 | { has_cmd bsdtar && prog=bsdtar; } || 200 | { has_cmd unzip && prog=unzip; } ;; 201 | debian*-package) 202 | { has_cmd ar || has_cmd bsdtar; } && cmd=(isdeb "$2" "$file2") ;; 203 | rar) 204 | { has_cmd bsdtar && prog=bsdtar; } || 205 | { has_cmd unrar && prog=unrar; } || 206 | { has_cmd rar && prog=rar; } ;; 207 | ms-cab-compressed) 208 | { has_cmd bsdtar && prog=bsdtar; } || 209 | { has_cmd cabextract && prog=cabextract; } ;; 210 | iso9660-image) 211 | { has_cmd bsdtar && prog=bsdtar; } || 212 | { has_cmd isoinfo && prog=isoinfo; } ;; 213 | cpio) 214 | { has_cmd cpio && prog=cpio; } || 215 | { has_cmd bsdtar && prog=bsdtar; } ;; 216 | archive) 217 | prog='ar' 218 | has_cmd bsdtar && prog=bsdtar ;; 219 | appimage|snap) 220 | has_cmd unsquashfs && cmd=(isimage "$x" "$2" "$file2") ;; 221 | esac 222 | # 7z formats and fall back to 7z supported formats 223 | if [[ -z $prog ]]; then 224 | case "$x" in 225 | 7z-compressed|lzma|xz|cab|arj|bzip2|cpio|iso) 226 | { has_cmd 7zz && prog=7zz; } || 227 | { has_cmd 7zr && prog=7zr; } || 228 | { has_cmd 7z && prog=7z; } || 229 | { has_cmd 7za && prog=7za; } ;; 230 | esac 231 | fi 232 | if [[ "$prog" = ar && "$2" = *@* ]]; then 233 | t=$(nexttmp) 234 | cat "$2" > "$t" 235 | set "$2" "$t" 236 | fi 237 | [[ -n $prog ]] && cmd=(isarchive "$prog" "$2" "$file2") 238 | if [[ -n ${cmd[*]} ]]; then 239 | [[ -n "$file2" ]] && file2= && return 240 | colorizer='cat' 241 | fi 242 | } 243 | 244 | isfinal () { 245 | [[ -n $cmd && $colorizer == 'cat' ]] && "${cmd[@]}" 246 | } 247 | 248 | isarchive () { 249 | prog=$1 250 | if [[ -n $3 ]]; then 251 | case $prog in 252 | tar|bsdtar) 253 | $prog Oxf "$2" -- "$3" 2>/dev/null;; 254 | rar|unrar) 255 | istemp "$prog p -inul" "$2" "$3" ;; 256 | ar) 257 | istemp "ar p" "$2" "$3" ;; 258 | unzip) 259 | istemp "unzip -avp" "$2" "$3" ;; 260 | cabextract) 261 | istemp cabextract2 "$2" "$3" ;; 262 | isoinfo) 263 | istemp "isoinfo -i" "$2" "-x$3" ;; 264 | cpio) 265 | if [[ "$2" == - ]]; then 266 | cpio -i --quiet --to-stdout "$3" 267 | else 268 | cpio -i --quiet --to-stdout --file "$2" "$3" 269 | fi ;; 270 | 7zz|7za|7zr) 271 | istemp "$prog e -so" "$2" "$3" 272 | esac 273 | else 274 | case $prog in 275 | tar|bsdtar) 276 | $prog tf "$2" ;; 277 | rar|unrar) 278 | istemp "$prog v" "$2"|grep -E ':[0-9][0-9] '|cut -c 67- ;; 279 | ar) 280 | istemp "ar t" "$2" ;; 281 | unzip) 282 | istemp "unzip -Z -1" "$2" ;; 283 | cabextract) 284 | istemp "cabextract -l" "$2" |grep -E '[0-9] \|'|sed 's/.*| //' ;; 285 | isoinfo) 286 | t="$2" 287 | istemp "isoinfo -d -i" "$2" >/dev/null 288 | istemp "isoinfo -d -i" "$t"| grep -E '^Joliet' && joliet=J 289 | isoinfo -fR"$joliet" -i "$t" ;; 290 | cpio) 291 | cpio -t --quiet < "$2" ;; 292 | 7zz|7za|7zr) 293 | istemp "$prog l" "$2"|grep -E '^[0-9][0-9][0-9[0-9]'|cut -c 54-|grep -Ev ' files$' 294 | esac 295 | fi 296 | } 297 | 298 | cabextract2 () { 299 | cabextract -pF "$2" "$1" 300 | } 301 | 302 | isimage () { 303 | if [[ "$1" == appimage ]]; then 304 | offset="-o $("$2" --appimage-offset)" 305 | fi 306 | if [[ -z "$3" ]]; then 307 | istemp "unsquashfs -d . -lc $offset" "$2" 308 | else 309 | istemp "unsquashfs -cat $offset" "$2" "$3" 310 | fi 311 | } 312 | 313 | isrpm () { 314 | if [[ -z "$2" ]]; then 315 | if has_cmd bsdtar; then 316 | bsdtar tf "$1" 317 | else 318 | rpm2cpio "$1" 2>/dev/null|cpio -i -t 2>/dev/null 319 | fi 320 | elif has_cmd bsdtar; then 321 | bsdtar xOf "$1" "$2" 322 | else 323 | rpm2cpio "$1" 2>/dev/null|cpio -i --quiet --to-stdout "$2" 324 | fi 325 | } 326 | 327 | isdeb () { 328 | if [[ "$1" = - ]]; then 329 | t=$(nexttmp) 330 | cat > "$t" 331 | set "$t" "$2" 332 | fi 333 | if has_cmd bsdtar; then 334 | data=$(bsdtar tf "$1" "data*") 335 | if [[ -z "$2" ]]; then 336 | bsdtar xOf "$1" "$data" | bsdtar tf - 337 | else 338 | bsdtar xOf "$1" "$data" | bsdtar xOf - "$2" 339 | fi 340 | else 341 | if [[ "$1" = *@* ]]; then 342 | t=$(nexttmp) 343 | cat "$1" > "$t" 344 | set "$1" "$t" 345 | fi 346 | data=$(ar t "$1"|grep data) 347 | ft=$(ar p "$1" "$data" | filetype -) 348 | get_unpack_cmd "$ft" - 349 | if [[ -z "$2" ]]; then 350 | ar p "$1" "$data" | "${cmd[@]}" | tar tf - 351 | else 352 | ar p "$1" "$data" | "${cmd[@]}" | tar xOf - "$2" 353 | fi 354 | fi 355 | } 356 | 357 | # the main program 358 | set +o noclobber 359 | setopt sh_word_split 2>/dev/null 360 | a="${1/#\~/$HOME}" 361 | a="${a//\\}" 362 | sep=: # file name separator 363 | altsep='=' # alternate separator character 364 | if [[ -e "$a" && "$a" == *"$sep"* ]]; then 365 | sep=$altsep 366 | elif [[ "$a" == *"$altsep"* ]]; then 367 | [[ -e "${a%%"$altsep"*}" ]] && sep=$altsep 368 | fi 369 | 370 | tmpdir=${TMPDIR:-/tmp}/lesspipe."$RANDOM" 371 | [[ -d "$tmpdir" ]] || mkdir "$tmpdir" 372 | [[ -d "$tmpdir" ]] || exit 1 373 | trap 'rm -rf "$tmpdir";exit 1' INT 374 | trap 'rm -rf "$tmpdir"' EXIT 375 | trap - PIPE 376 | 377 | t=$(nexttmp) 378 | # make LESSOPEN="|- ... " work 379 | if [[ $LESSOPEN == *\|-* && $1 == - ]]; then 380 | cat > "$t" 381 | [[ -n "$fext" ]] && t="$t$sep$fext" 382 | a="$t" 383 | nexttmp >/dev/null 384 | fi 385 | 386 | show "$a" 387 | -------------------------------------------------------------------------------- /lesspipe.1: -------------------------------------------------------------------------------- 1 | .TH LESSPIPE.SH "1" "August 2024" "lesspipe.sh" "User Commands" 2 | .SH NAME 3 | lesspipe.sh \- a filter for less 4 | .SH SYNOPSIS 5 | .B lesspipe.sh 6 | [\fIFILE[s]\fR]... 7 | .SH DESCRIPTION 8 | .PP 9 | The aim of \fBlesspipe.sh\fP is to enhance the output of \fBless\fP. The choice 10 | of the rules to be applied to modify the output are based on the file contents. 11 | The file extension is respected only as a last resort. 12 | Usually \fBlesspipe.sh\fP is called as an input filter to \fBless\fP. 13 | .PP 14 | With the help of that filter \fBless\fP 15 | will display the uncompressed contents of compressed (\fIgzip, bzip2, 16 | compress, zstd, lz4, lzip, xz, lzma or brotli\fP) files. For files 17 | containing archives and directories, a table of contents will be displayed 18 | (\fItar, ar, zip, 7-zip, rar, jar, cpio, rpm, deb ms-cabinet, iso, appimage 19 | and snap formats\fP). 20 | Many other files will be reformatted for display. It includes 21 | \fIpdf, dvi, markdown, Office (MS and Openoffice)\fP suites formats, 22 | \fINetCDF, matlab, device tree blob, html, xml\fP and \fImedia (image, audio and 23 | video)\fP formats. This does require helper programs being installed. 24 | .PP 25 | The filter can also be applied recursively to extract and display 26 | files in archives on the fly. This works to a depth of 6 where applying a 27 | decompression algorithm counts as a separate level. 28 | .PP 29 | If the file utility reports text with an encoding different from the one 30 | used in the terminal then the text will be transformed using \fIiconv\fP into 31 | the default encoding. This does assume the \fIfile\fP command gets the file 32 | encoding right, which can be wrong in some situations. An appended colon 33 | to the file name does suppress the conversion. 34 | .PP 35 | When using the programs \fBgit\fP, \fBvim\fP or \fBmutt\fP they can be 36 | enabled to read non-text files by using lesspipe.sh. That is described in 37 | the Wiki at \fIhttps://github.com/wofr06/lesspipe/wiki\fP. 38 | .SH FILTER ACTIVATION 39 | The filter is called from \fBless\fP provided the environment variable 40 | \fBLESSOPEN\fP is set properly. For ksh like shells (\fIbash, zsh\fP) 41 | the command 42 | .RS 43 | .I LESSOPEN="|lesspipe.sh %s"; export LESSOPEN 44 | .RE 45 | does activate the filter for less. Use the fully qualified path, if 46 | \fBlesspipe.sh\fP is not in the search path. The command to set \fBLESSOPEN\fP 47 | can also be displayed by calling \fBlesspipe.sh\fP without arguments. 48 | This can even be used to set \fBLESSOPEN\fP directly: 49 | .RS 50 | .I eval `lesspipe.sh` 51 | (bash) or 52 | .RE 53 | .RS 54 | .I lesspipe.sh|source /dev/stdin 55 | (zsh) 56 | .RE 57 | The above commands work only in the described manner if the file name is 58 | \fBlesspipe.sh\fP. 59 | If it is installed under a different name then calling it without an argument 60 | will work as a filter with \fBLESSQUIET\fP set and expecting input from STDIN. 61 | .PP 62 | Having set the environment variable as described above, \fBless\fP 63 | :will then display textual information for a wide range of file formats. 64 | .PP 65 | The filter is normally not called if input is piped to less as in 66 | .RS 67 | .I cat somefile | less 68 | .RE 69 | As described in the man page of less, the filtering in a pipe can however 70 | be forced by starting \fBLESSOPEN\fP with the characters \fI|-\fP. 71 | .PP 72 | \fBLESSOPEN\fP starting with the two characters \fI||\fP to handle empty files 73 | and command errors is implemented only partly, usually on failures of 74 | commands within \fBlesspipe.sh\fP the error messages get displayed. 75 | .PP 76 | To suppress informal messages in the first line of the filter output the 77 | ENV variable \fBLESSQUIET\fP can be set to a nonempty value. 78 | .PP 79 | To disengage the filter temporarily a colon can be appended to the file name. 80 | If the file name contains a colon, then an equal sign should be used instead. 81 | .SH HTML, XML and Perl POD Files 82 | Files in the \fIhtml\fP, \fIxml\fP and \fIperl pod\fP format are always 83 | rendered. Sometimes however the original contents of the file should be viewed 84 | instead. As mentioned before that can be achieved by appending a colon to the 85 | file name. If the correct file type (\fIhtml\fP, \fIxml\fP, \fIpod\fP) follows, 86 | the output can get colorized (see also the section below). 87 | .PP 88 | .RS 89 | Example: \fIless index.html:html\fP 90 | .RE 91 | .PP 92 | If the binary \fIxmq\fP is installed, then \fIxml\fP is rendered differently, 93 | so that the xml structure is better recognized. A similar display for 94 | \fIhtml\fP contents using \fIxmq\fP is achieved by appending a colon to the 95 | file name. To get the original html file contents, two colons are 96 | required in this case. 97 | .SH OUTPUT COLORIZATION 98 | The filter is able to do syntax highlighting for a wide variety of 99 | file types. If installed, \fInvimpager\fP is used for 100 | colorizing the output. If not, \fIbat\fP/\fIbatcat\fP, \fIpygmentize\fP, 101 | \fIsource-highlight\fP, \fIvimcolor\fP and \fIcode2color\fP are tried in turn. 102 | For bat/batcat the theme is set to \fIansi\fP and the style is set to 103 | \fIplain\fP which comes closer to the unfiltered output of \fBless\fP. 104 | These settings can be changed in \fI~/.config/bat/config\fP or by the 105 | environment variables \fBBAT_STYLE\fP and \fBBAT_THEME\fP. 106 | .PP 107 | Among the colorizers 108 | a preferred one can be forced for colorizing by setting the ENV variable 109 | \fBLESSCOLORIZER\fP to the name of the colorizer. For \fIpygmentize\fP and 110 | \fIbat\fP/\fIbatcat\fP restricted option settings are allowed as follows: 111 | .RS 112 | .I LESSCOLORIZER='pygmentize -O style=foo' (-P allowed as well) 113 | .RE 114 | .RS 115 | .I LESSCOLORIZER='bat --style=foo --theme=bar' (default is theme ansi, style plain) 116 | .RE 117 | Syntax highlighting is activated, if the environment variable \fBLESS\fP 118 | exists and contains the option \fI-R\fP 119 | or less is called with this option. This guarantees that escape sequences 120 | get converted into colors and do not garble the display. Using the option 121 | \fI-r\fP is not recommended, as the screen layout may be wrong, if long 122 | lines are in the output. 123 | .PP 124 | Syntax highlighting can be switched off by 125 | appending a colon after the file name, if the output was colorful. If the 126 | wrong language was chosen for syntax highlighting, then another one can be 127 | forced by appending a colon and a suffix to the file name. 128 | .PP 129 | In a pipe that method cannot be used. As a way out a last argument can be added 130 | that gets inspected by \fBlesspipe.sh\fP. 131 | A single colon (disengage filter) or :extension (force language) is possible as e.g with 132 | .RS 133 | .I command that generates c code | less - :c 134 | .RE 135 | .PP 136 | When the conditions for syntax highlighting are met directory listings and 137 | listings of tar file contents get colorized as well. 138 | .PP 139 | As \fBless\fP is used as a default browser in other programs (e.g. \fIman\fP, 140 | \fIgit\fP, and \fIperldoc\fP)) \fBlesspipe.sh\fP may be engaged and alter 141 | the output of those programs. 142 | .SH WATCHING GROWING FILES 143 | As soon as \fBlesspipe.sh\fP 144 | calls a program to convert the input the ability to watch growing files 145 | (using the F command within less) is lost. This is usually wanted for log 146 | files like syslog. To temporarily disengage \fBlesspipe.sh\fP 147 | a colon as the last argument for \fBless\fP needs to be added as e.g in 148 | .RS 149 | .I less /var/log/syslog : 150 | .RE 151 | or \fBless\fP 152 | can be called with the +F argument, which is equivalent to F within the pager: 153 | .RS 154 | .I less +F /var/log/syslog 155 | .RE 156 | Appending a colon to the file name does not work, as then the filter has to be engaged to at least remove that colon and use cat for the original file. 157 | On the other hand non growing log files can be colorized using \fBccze\fP. 158 | Its recognition as a log file is difficult if not ending in \fI.log\fP 159 | but can be forced appending \fB:.log\fP to the file name as e.g in 160 | .RS 161 | .I less /var/log/syslog:.log 162 | .RE 163 | .SH ADVANCED USAGE 164 | This version of \fBlesspipe.sh\fP 165 | allows you to view individual files contained in a file archive, which itself 166 | may even be contained in another archive. 167 | .PP 168 | The notation for viewing files in multifile archives is 169 | .RS 170 | .B less 171 | \fIarchive_file\fP:\fIcontained_file\fP 172 | .RE 173 | or even 174 | .RS 175 | .B less 176 | \fIsuper_archive\fP:\fIarchive_file\fP:\fIcontained_file\fP 177 | .RE 178 | To display the last file in the chain raw format, a colon (\fI:\fP) has to be 179 | appended to the file name. If it does contain a colon, then the alternate 180 | separator character equal sign (\fI=\fP) has to be used. 181 | .PP 182 | Again, this method of extracting and displaying files does not work if 183 | \fBless\fP is called in an output pipe and \fBLESSOPEN\fP starts with the 184 | \fB|-\fP characters. As already for syntax highlighting the solution is to use 185 | a second argument that starts with a colon. Then the above command would 186 | be written as 187 | .RS 188 | \fBcat \fIsuper_archive\fP | \fBless\fP - :\fIarchive\fP:\fIcontained_file\fP 189 | .RE 190 | .PP 191 | .SH COMPLETING MECHANISM FOR ARCHIVE CONTENTS 192 | With the provided \fIlesscomplete\fP (for \fBzsh\fP and \fBbash\fP), 193 | \fI_less\fP (for \fBzsh\fP) and \fIless_completion\fP (for \fBbash\fP) files 194 | a tab completion for files in archives can be accomplished. 195 | Entering a colon (:) or an equal sign (=) after an archive 196 | file name and then pressing the tab key triggers the completion mechanism. 197 | This also works in chained archives. The files \fIlesscomplete\fP and 198 | \fIless_completion\fP have to be in one of the directories listed in 199 | \fB$PATH\fP and the function \fI_less\fP for \fBzsh\fP in a directory 200 | listed by \fI$fpath\fP. The less_completion script has to 201 | be sourced within a bash initialization script, e.g. in \fI~/.bashrc\fP. New 202 | directories such as \fI~/scripts\fP and \fI~/.fpath\fP can be added using the 203 | commands 204 | .RS 205 | \fBPATH\fP=\fI~/scripts:$PATH\fP and 206 | \fBfpath\fP=\fI(~/.fpath $fpath)\fP 207 | .RE 208 | .SH USER DEFINED FILTERING 209 | The lesspipe.sh filtering can be replaced or enhanced by a user defined 210 | program. Such a program has to be called either \fB.lessfilter\fP (and be 211 | placed in the users home directory), or \fBlessfilter\fP (and be accessible 212 | from a directory mentioned in \fB$PATH\fP). 213 | It has to be executable and has to end with an exit code 0, if the filtering 214 | was done within that script. Otherwise, a nonzero exit code means the filtering 215 | is left to lesspipe.sh. 216 | .PP 217 | This mechanism can be used to add filtering for new formats or e.g. inhibit 218 | filtering for certain file types. 219 | .SH AUTHOR 220 | Wolfgang Friebel 221 | .SH "REPORTING BUGS" 222 | Report bugs to . 223 | .SH COPYRIGHT 224 | Copyright \(co 2005-2024 Wolfgang Friebel 225 | .br 226 | This is free software; see the source for copying conditions. There is NO 227 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 228 | .SH "SEE ALSO" 229 | less(1) 230 | .PP 231 | A description of \fBlesspipe.sh\fP 232 | is also contained in the file README contained in the source code package 233 | -------------------------------------------------------------------------------- /lesspipe.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # lesspipe.sh, a preprocessor for less 3 | lesspipe_version=2.18 4 | # Author: Wolfgang Friebel (wp.friebel AT gmail.com) 5 | 6 | has_cmd () { 7 | [[ -n "$2" && "$2" > $($1 --version 2>/dev/null) ]] && return 1 8 | command -v "$1" > /dev/null 9 | } 10 | 11 | fileext () { 12 | fn=${1##*/} 13 | case "$fn" in 14 | .*.*) extension=${fn##*.} ;; 15 | .*) extension=${fn#.} ;; 16 | *.*) extension=${fn##*.} ;; 17 | esac 18 | echo "$extension" 19 | } 20 | 21 | filetype () { 22 | # do not depend on the file extension, if possible 23 | fname="$1" 24 | if [[ "$1" == - || -z $1 ]]; then 25 | declare t 26 | t=$(nexttmp) 27 | head -c 1000000 > "$t" 2>/dev/null 28 | [[ -z $fileext ]] && fname="$t" || fname="$fileext" 29 | set "$t" "$2" 30 | fi 31 | fext=$(fileext "$fname") 32 | ### get file type from mime type 33 | declare ft 34 | ft=$(file -L -s -b --mime "$1" 2> /dev/null) 35 | [[ $ft == *=* ]] && fchar="${ft##*=}" || fchar=utf-8 36 | fcat="${ft%/*}" 37 | ft="${ft#*/}"; ft="${ft%;*}"; ft="${ft#x-script.}"; ft="${ft#x-}" 38 | ftype="${ft#vnd\.}" 39 | # chose better name 40 | case "$ftype" in 41 | openxmlformats-officedocument.wordprocessingml.document) 42 | ftype=docx ;; 43 | openxmlformats-officedocument.presentationml.presentation) 44 | ftype=pptx ;; 45 | openxmlformats-officedocument.spreadsheetml.sheet) 46 | ftype=xlsx ;; 47 | oasis.opendocument.text*) 48 | ftype=odt ;; 49 | oasis.opendocument.spreadsheet) 50 | ftype=ods ;; 51 | oasis.opendocument.presentation) 52 | ftype=odp ;; 53 | sun.xml.writer) 54 | ftype=ooffice1 ;; 55 | shellscript) 56 | ftype='sh' ;; 57 | makefile) 58 | ftype='make' ;; 59 | epub+zip) 60 | ftype=epub ;; 61 | matlab-data) 62 | ftype=matlab ;; 63 | # file may report wrong type for given file names (ok in file 5.39) 64 | troff) 65 | case "${fname##*/}" in 66 | [Mm]akefile|[Mm]akefile.*|BSDMakefile) 67 | ftype='make' ;; 68 | esac 69 | esac 70 | # correct for a more specific file type 71 | case "$fext" in 72 | epub) 73 | [[ $ftype == zip ]] && ftype=epub ;; 74 | ipynb) 75 | [[ $ftype == json ]] && ftype=ipynb ;; 76 | mp3) 77 | [[ $ftype == mpeg ]] && ftype=mp3 ;; 78 | jsx|csv) 79 | [[ $fcat == text ]] && ftype="$fext" ;; 80 | tsx) 81 | [[ $fcat == text ]] && ftype=typescript-jsx ;; 82 | appimage|AppImage) 83 | [[ $fcat == application && -x "$1" ]] && ftype=appimage ;; 84 | snap) 85 | [[ $fcat == application ]] && ftype="$fext" ;; 86 | esac 87 | ### get file type from 'file' command for an unspecific result 88 | [[ "$fcat" == message && $ftype == plain ]] && ftype=msg 89 | [[ "$fcat" == message && $ftype == rfc822 ]] && fcat=text && ftype=email 90 | if [[ "$fcat" == application && "$ftype" == octet-stream || "$fcat" == text && $ftype == plain ]]; then 91 | ft=$(file -L -s -b "$1" 2> /dev/null) 92 | # first check if the file command yields something 93 | case $ft in 94 | *mat-file*) 95 | ftype=matlab ;; 96 | *POD\ document*) 97 | ftype=pod ;; 98 | *PEM\ certificate\ request) 99 | ftype=csr ;; 100 | *PEM\ certificate) 101 | ftype=x509 ;; 102 | *Microsoft\ OOXML) 103 | ftype=docx ;; 104 | Apple\ binary\ property\ list) 105 | ftype=plist ;; 106 | PGP\ *ncrypted*|GPG\ encrypted*) 107 | ftype=pgp ;; 108 | Audio\ file\ with\ ID3\ *) 109 | ftype=mp3 ;; 110 | OpenOffice.org\ 1.x\ Writer\ document) 111 | ftype=ooffice1 ;; 112 | *osascript*) 113 | ftype=applescript ;; 114 | *Device\ Tree\ Blob*) 115 | ftype=dtb ;; 116 | # if still unspecific, determine file type by extension 117 | data) 118 | ### binary only file formats, type not guessed by 'file' 119 | case "$fext" in 120 | mat) 121 | ftype=matlab ;; 122 | br|bro|tbr) 123 | ftype=brotli ;; 124 | lz4|lt4|tz4|tlz4) 125 | ftype=lz4 ;; 126 | esac 127 | esac 128 | ### decide file type based on extension 129 | # binary or text file formats 130 | case "$fext" in 131 | crt|pem) 132 | ftype=x509 ;; 133 | crl|csr) 134 | ftype="$fext" ;; 135 | esac 136 | if [[ $fchar != binary ]]; then 137 | # text only file formats 138 | case "$fext" in 139 | html|htm|xml|pod|log) 140 | ftype="$fext" ;; 141 | pm) 142 | ftype=perl ;; 143 | md|MD|mkd|markdown|rst) 144 | ftype=markdown ;; 145 | ebuild|eclass) 146 | ftype='sh' ;; 147 | esac 148 | fi 149 | fi 150 | 151 | echo "$ftype:$fchar:$fcat" 152 | } 153 | 154 | msg () { 155 | [[ -n "$LESSQUIET" ]] && return; 156 | if [[ -n "$lesspipe_version" ]]; then 157 | echo "==> (lesspipe $lesspipe_version) $*" 158 | else 159 | echo "==> $*" 160 | fi 161 | lesspipe_version= 162 | } 163 | 164 | separatorline () { 165 | declare a="===================================" 166 | word="Contents" 167 | [[ -n $1 ]] && word=$1 168 | echo "$a $word $a" 169 | } 170 | 171 | nexttmp () { 172 | declare new="$tmpdir/lesspipe.$RANDOM.${ft%%:*}" 173 | echo "$new" 174 | } 175 | 176 | istemp () { 177 | prog="$1" 178 | shift 179 | if [[ "$1" == - ]]; then 180 | shift 181 | t=$(nexttmp) 182 | cat > "$t" 183 | $prog "$t" "$@" 184 | else 185 | $prog "$@" 186 | fi 187 | } 188 | 189 | nodash () { 190 | prog="$1" 191 | shift 192 | [[ "$1" == - ]] && shift 193 | $prog "$@" 194 | } 195 | 196 | show () { 197 | if [[ "$1" == https://* ]]; then 198 | x=html 199 | isfinal "$1" 200 | return 201 | fi 202 | file1="${1%%"$sep"*}" 203 | rest1="${1#"$file1"}" 204 | while [[ "$rest1" == "$sep$sep"* ]]; do 205 | [[ "$rest1" == "$sep$sep" ]] && break 206 | rest1="${rest1#"$sep$sep"}" 207 | file1="${rest1%%"$sep"*}" 208 | rest1="${rest1#"$file1"}" 209 | file1="${1%"$rest1"}" 210 | done 211 | [[ ! -e "$file1" && "$file1" != '-' ]] && exit 1 212 | rest11="${rest1#"$sep"}" 213 | file2="${rest11%%"$sep"*}" 214 | rest2="${rest11#"$file2"}" 215 | while [[ "$rest2" == "$sep$sep"* ]]; do 216 | [[ "$rest2" == "$sep$sep" ]] && break 217 | rest2="${rest2#"$sep$sep"}" 218 | file2="${rest2%%"$sep"*}" 219 | rest2="${rest2#"$file2"}" 220 | file2="${rest11%"$rest2"}" 221 | done 222 | rest2="${rest11#"$file2"}" 223 | rest11="$rest1" 224 | 225 | if [[ "${cmd[*]}" == "" ]]; then 226 | ft=$(filetype "$file1") 227 | get_unpack_cmd "$ft" "$file1" "$rest1" 228 | if [[ "${cmd[*]}" != "" ]]; then 229 | show "-$rest1" 230 | else 231 | # if nothing to convert, exit without a command 232 | isfinal "$file1" "$rest11" 233 | fi 234 | elif [[ "$c1" == "" ]]; then 235 | c1=("${cmd[@]}") 236 | ft=$("${c1[@]}" | filetype -) || exit 1 237 | get_unpack_cmd "$ft" "$file1" "$rest1" 238 | if [[ "${cmd[*]}" != "" ]]; then 239 | show "-$rest1" 240 | else 241 | "${c1[@]}" | isfinal - "$rest11" 242 | fi 243 | elif [[ "$c2" == "" ]]; then 244 | c2=("${cmd[@]}") 245 | ft=$("${c1[@]}" | "${c2[@]}" | filetype -) || exit 1 246 | get_unpack_cmd "$ft" "$file1" "$rest1" 247 | if [[ "${cmd[*]}" != "" ]]; then 248 | show "-$rest1" 249 | else 250 | "${c1[@]}" | "${c2[@]}" | isfinal - "$rest11" 251 | fi 252 | elif [[ "$c3" == "" ]]; then 253 | c3=("${cmd[@]}") 254 | ft=$("${c1[@]}" | "${c2[@]}" | "${c3[@]}" | filetype -) || exit 1 255 | get_unpack_cmd "$ft" "$file1" "$rest1" 256 | if [[ "${cmd[*]}" != "" ]]; then 257 | show "-$rest1" 258 | else 259 | "${c1[@]}" | "${c2[@]}" | "${c3[@]}" | isfinal - "$rest11" 260 | fi 261 | elif [[ "$c4" == "" ]]; then 262 | c4=("${cmd[@]}") 263 | ft=$("${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | filetype -) || exit 1 264 | get_unpack_cmd "$ft" "$file1" "$rest1" 265 | if [[ "${cmd[*]}" != "" ]]; then 266 | show "-$rest1" 267 | else 268 | "${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | isfinal - "$rest11" 269 | fi 270 | elif [[ "$c5" == "" ]]; then 271 | c5=("${cmd[@]}") 272 | ft=$("${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | "${c5[@]}" | filetype -) || exit 1 273 | get_unpack_cmd "$ft" "$file1" "$rest1" 274 | if [[ "${cmd[*]}" != "" ]]; then 275 | echo "$0: Too many levels of encapsulation" 276 | else 277 | "${c1[@]}" | "${c2[@]}" | "${c3[@]}" | "${c4[@]}" | "${c5[@]}" | isfinal - "$rest11" 278 | fi 279 | fi 280 | } 281 | 282 | get_unpack_cmd () { 283 | fchar="${1%:*}"; fchar="${fchar#*:}" 284 | fcat="${1##*:}" 285 | x="${1%%:*}" 286 | cmd=() 287 | [[ "$3" == $sep$sep ]] && return 288 | declare t 289 | # uncompress / transform 290 | case $x in 291 | gzip|bzip2|lzip|lzma|xz|brotli|compress) 292 | # remember name of uncompressed file 293 | [[ $2 == - ]] || fileext="$2" 294 | fileext=${fileext%%.gz}; fileext=${fileext%%.bz2} 295 | [[ $x == compress ]] && x=gzip 296 | has_cmd "$x" && cmd=("$x" -cd "$2") ;; 297 | zstd) 298 | has_cmd zstd && cmd=(zstd -cdqM1073741824 "$2") ;; 299 | lz4) 300 | has_cmd lz4 && cmd=(lz4 -cdq "$2") ;; 301 | # xls(x) output looks better if transformed to csv and then displayed 302 | xlsx) 303 | { has_cmd xlsx2csv 0.8.3 && cmd=(xlsx2csv "$2"); } || 304 | { has_cmd in2csv && cmd=(in2csv -f xlsx "$2"); } || 305 | { has_cmd excel2csv && cmd=(istemp excel2csv "$2"); } ;; 306 | ms-excel) 307 | { has_cmd in2csv && cmd=(in2csv -f xls "$2"); } || 308 | { has_cmd xls2csv && cmd=(istemp xls2csv "$2"); } ;; 309 | esac 310 | [[ ${cmd[*]} == '' ]] || return 311 | # convert into utf8 312 | 313 | if [[ -n $charmap && $fchar != binary && $fchar != *ascii && $fchar != "$charmap" && $fchar != unknown* ]]; then 314 | qm="\033[7m?\033[m" # inverted question mark 315 | rep=(-c) 316 | trans=() 317 | iconv --byte-subst - /dev/null && rep=(--unicode-subst="$qm" --byte-subst="$qm" --widechar-subst="$qm") # MacOS 318 | iconv -f "$fchar" -t "$charmap//TRANSLIT" - /dev/null && trans=(-t "$charmap//TRANSLIT") 319 | msg "append $sep$sep to filename to view the original $fchar encoded file" 320 | cmd=(iconv "${rep[@]}" -f "$fchar" "${trans[@]}" "$2") 321 | # loop protection, just in case 322 | charmap= 323 | return 324 | fi 325 | [[ "$3" == "$sep" ]] && return 326 | file2=${3#"$sep"} 327 | file2=${file2%%"$sep"*} 328 | # remember name of file to extract or file type 329 | [[ -n "$file2" ]] && fileext="$file2" 330 | # extract from archive 331 | rest1="$rest2" 332 | rest2= 333 | prog= 334 | case "$x" in 335 | tar) 336 | prog=tar 337 | has_cmd bsdtar && prog=bsdtar ;; 338 | rpm) 339 | { has_cmd cpio && has_cmd rpm2cpio; } || 340 | { has_cmd bsdtar; } && cmd=(isrpm "$2" "$file2") ;; 341 | java-archive|zip) 342 | { has_cmd bsdtar && prog=bsdtar; } || 343 | { has_cmd unzip && prog=unzip; } ;; 344 | debian*-package) 345 | { has_cmd ar || has_cmd bsdtar; } && cmd=(isdeb "$2" "$file2") ;; 346 | rar) 347 | { has_cmd bsdtar && prog=bsdtar; } || 348 | { has_cmd unrar && prog=unrar; } || 349 | { has_cmd rar && prog=rar; } ;; 350 | ms-cab-compressed) 351 | { has_cmd bsdtar && prog=bsdtar; } || 352 | { has_cmd cabextract && prog=cabextract; } ;; 353 | iso9660-image) 354 | { has_cmd bsdtar && prog=bsdtar; } || 355 | { has_cmd isoinfo && prog=isoinfo; } ;; 356 | cpio) 357 | { has_cmd cpio && prog=cpio; } || 358 | { has_cmd bsdtar && prog=bsdtar; } ;; 359 | archive) 360 | prog='ar' 361 | has_cmd bsdtar && prog=bsdtar ;; 362 | appimage|snap) 363 | has_cmd unsquashfs && cmd=(isimage "$x" "$2" "$file2") ;; 364 | esac 365 | # 7z formats and fall back to 7z supported formats 366 | if [[ -z $prog ]]; then 367 | case "$x" in 368 | 7z-compressed|lzma|xz|cab|arj|bzip2|cpio|iso) 369 | { has_cmd 7zz && prog=7zz; } || 370 | { has_cmd 7zr && prog=7zr; } || 371 | { has_cmd 7z && prog=7z; } || 372 | { has_cmd 7za && prog=7za; } ;; 373 | esac 374 | fi 375 | if [[ "$prog" = ar && "$2" = *@* ]]; then 376 | t=$(nexttmp) 377 | cat "$2" > "$t" 378 | set "$2" "$t" 379 | fi 380 | [[ -n $prog ]] && cmd=(isarchive "$prog" "$2" "$file2") 381 | if [[ -n ${cmd[*]} ]]; then 382 | [[ -n "$file2" ]] && file2= && return 383 | msg "use ${x}_file${sep}contained_file to view a file in the archive" 384 | if [[ $COLOR = --color=always ]]; then 385 | has_cmd archive_color && colorizer=(archive_color) 386 | fi 387 | fi 388 | } 389 | 390 | analyze_args () { 391 | # determine how we are called 392 | cmdtree=$(ps -T -oargs= 2>/dev/null) 393 | while read -r line; do 394 | arg1=${line%% *}; arg1=${arg1##*/} 395 | [[ $arg1 == less ]] && lessarg=$line 396 | done <<< "$cmdtree" 397 | # return if we want to watch growing files 398 | [[ $lessarg == *less\ *\ +F\ * || $lessarg == *less\ *\ : ]] && exit 0 399 | # color is set when calling less with -r or -R or LESS contains that option 400 | COLOR="--color=auto" 401 | has_cmd tput && colors=$(tput colors) || colors=0 402 | if [[ $colors -ge 8 ]]; then 403 | lessarg="$LESS $lessarg" 404 | # shellcheck disable=SC2206 405 | r_string=($lessarg) 406 | for i in "${r_string[@]}" 407 | do 408 | [[ $i =~ ^-[A-Za-z~]*[rR] || $i = --raw-control-chars || $i = --RAW-CONTROL-CHARS ]] && COLOR="--color=always" 409 | done 410 | fi 411 | # last argument starting with colon or equal sign is used for piping into less 412 | [[ $lessarg == *\ [:=]* ]] && fext=${lessarg#*[:=]} 413 | } 414 | 415 | has_colorizer () { 416 | [[ $COLOR == *always ]] || return 417 | [[ $2 == plain || -z $2 ]] && return 418 | prog=${LESSCOLORIZER%% *} 419 | 420 | for i in nvimpager bat batcat pygmentize source-highlight vimcolor code2color ; do 421 | [[ -z $prog || $prog == "$i" ]] && has_cmd "$i" && prog=$i 422 | done 423 | [[ "$2" =~ ^[0-9]*$ || -z "$2" ]] || lang=$2 424 | # prefer an explicitly requested language 425 | [[ -n $3 ]] && lang=$3 || lang=$2 426 | case $prog in 427 | bat|batcat) 428 | batconfig=$($prog --config-file) 429 | [[ -n $lang ]] && $prog --list-languages|sed 's/.*:/,/;s/$/,/'|grep -i ",$lang," > /dev/null && opt=(-l "$lang") 430 | opt2=${LESSCOLORIZER##*--} 431 | [[ $opt2 == style=* ]] && style=${opt2##*=} 432 | [[ $opt2 == theme=* ]] && theme=${opt2##*=} 433 | opt2=$(echo "$LESSCOLORIZER"|tr -s ' ') 434 | opt2=${opt2%[ ]--*} 435 | opt2=${opt2##*--} 436 | [[ $opt2 == style=* ]] && style=${opt2##*=} 437 | [[ $opt2 == theme=* ]] && theme=${opt2##*=} 438 | [[ -n $theme ]] && theme=$(echo "${theme##*=}"|tr -d '/"\047\134/') 439 | [[ -z $style ]] && style=$BAT_STYLE 440 | [[ -z $theme ]] && theme=$BAT_THEME 441 | if [[ -r "$batconfig" ]]; then 442 | if [[ -z $style ]]; then 443 | grep -q -e '^--style' "$batconfig" || style=plain 444 | fi 445 | if [[ -z $theme ]]; then 446 | grep -q -e '^--theme' "$batconfig" || theme=ansi 447 | fi 448 | else 449 | [[ -z $style ]] && style=plain 450 | [[ -z $theme ]] && theme=ansi 451 | fi 452 | style="${style%% *}" theme="${theme%%[|&;<>]*}" 453 | opt+=(${style:+--style="$style"} ${theme:+--theme="$theme"}) 454 | opt+=("$COLOR" --paging=never "$1") ;; 455 | pygmentize) 456 | pygmentize -l "$lang" /dev/null &>/dev/null && opt=(-l "$lang") || opt=(-g) 457 | [[ -n $LESSCOLORIZER && $LESSCOLORIZER = *-[OP]\ *style=* ]] && style="${LESSCOLORIZER/*style=/}" 458 | [[ -n $style ]] && opt+=(-O style="${style%% *}") 459 | [[ $colors -ge 256 ]] && opt+=(-f terminal256) 460 | [[ "$1" == - ]] || opt+=("$1") ;; 461 | source-highlight) 462 | [[ -n $1 && "$1" != - ]] && opt=(-i "$1") || opt=() 463 | [[ -n $lang ]] && opt+=(-s "$lang") 464 | style=esc 465 | [[ $colors -ge 256 ]] && style=esc256 466 | opt+=(--failsafe -f "$style") ;; 467 | code2color|vimcolor) 468 | opt=("$1") 469 | [[ -n "$3" ]] && opt=(-l "$3" "$1") ;; 470 | nvimpager) 471 | opt=(-c "$1") 472 | [[ -n "$3" ]] && ft=${3##*/} && ft=${ft##*.} && 473 | opt=(-c "$1" --cmd "set filetype=$ft") ;; 474 | *) 475 | return ;; 476 | esac 477 | colorizer=("$prog" "${opt[@]}") 478 | } 479 | 480 | isfinal () { 481 | if [[ "$2" == *$sep ]]; then 482 | if [[ "$2" == "$sep" && "$x" == html ]]; then 483 | [[ $COLOR == *always ]] && colarg="--color" || colarg="--mono" 484 | has_cmd xmq && isxmq "$1" html && return 485 | fi 486 | cat "$1" 487 | return 488 | fi 489 | if [[ -z "${cmd[*]}" ]]; then 490 | # respect extension set by user 491 | [[ -n "$file2" && "$fileext" == "$file2" && "$fileext" != *.* ]] && x="$fileext" 492 | case "$x" in 493 | directory) 494 | cmd=(ls -lA "$COLOR" "$1") 495 | if ! ls "$COLOR" > /dev/null 2>&1; then 496 | cmd=(CLICOLOR_FORCE=1 ls -lA -G "$1") 497 | if ! ls -lA -G > /dev/null 2>&1; then 498 | cmd=(ls -lA "$1") 499 | fi 500 | fi 501 | msg="$x: showing the output of ${cmd[*]}" ;; 502 | xml) 503 | [[ -z $file2 ]] && 504 | { { has_cmd xmq && cmd=(isxmq "$1" xml); } || 505 | { has_htmlprog && cmd=(ishtml "$1"); }; } ;; 506 | html) 507 | [[ -z $file2 ]] && has_htmlprog && cmd=(ishtml "$1") ;; 508 | dtb|dts) 509 | has_cmd dtc && cmd=(isdtb "$1") ;; 510 | pdf) 511 | { has_cmd pdftotext && cmd=(istemp pdftotext -layout -nopgbrk -q -- "$1" -); } || 512 | { has_cmd pdftohtml && has_htmlprog && cmd=(istemp ispdf "$1"); } || 513 | { has_cmd pdfinfo && cmd=(istemp pdfinfo "$1"); } ;; 514 | postscript) 515 | has_cmd ps2ascii && nodash ps2ascii "$1" 2>/dev/null ;; 516 | java-applet) 517 | # filename needs to end in .class 518 | has_cmd procyon && t=$t.class && cat "$1" > "$t" && cmd=(procyon "$t") ;; 519 | markdown) 520 | [[ $COLOR = *always ]] && mdopt=(--ansi ) || mdopt=(-c) 521 | { has_cmd mdcat && cmd=(mdcat "${mdopt[@]}" "$1"); } || 522 | { has_cmd pandoc && cmd=(pandoc -t plain "$1"); } ;; 523 | docx) 524 | { has_cmd pandoc && cmd=(pandoc -f docx -t plain "$1"); } || 525 | { has_cmd docx2txt && cmd=(docx2txt "$1" -); } || 526 | { has_cmd libreoffice && cmd=(isoffice2 "$1"); } ;; 527 | pptx) 528 | { has_cmd pptx2md && t2=$(nexttmp) && 529 | { { has_cmd mdcat && istemp "pptx2md --disable-image --disable-wmf \ 530 | -o $t2" "$1" && cmd=(mdcat "$t2"); } || 531 | { has_cmd pandoc && istemp "pptx2md --disable-image --disable-wmf \ 532 | -o $t2" "$1" && cmd=(pandoc -f markdown -t plain "$t2"); } }; } || 533 | { can_do_office && cmd=(isoffice "$1" ppt); } ;; 534 | xlsx|ods) 535 | { has_cmd xlscat && cmd=(istemp "xlscat -L -R all" "$1"); } || 536 | { can_do_office && cmd=(isoffice "$1" "$x"); } ;; 537 | odt) 538 | { has_cmd odt2txt && cmd=(istemp odt2txt "$1"); } || 539 | { has_cmd pandoc && cmd=(pandoc -f odt -t plain "$1"); } || 540 | { has_cmd libreoffice && cmd=(isoffice2 "$1"); } ;; 541 | odp) 542 | { can_do_office && cmd=(isoffice "$1" odp); } ;; 543 | msword) 544 | t="$1"; [[ "$t" == - ]] && t=/dev/stdin 545 | { has_cmd wvText && cmd=(istemp wvText "$t" /dev/stdout); } || 546 | { has_cmd catdoc && cmd=(catdoc "$1"); } || 547 | { has_cmd libreoffice && cmd=(isoffice2 "$1"); } ;; 548 | ms-powerpoint) 549 | { can_do_office && cmd=(isoffice "$1" ppt); } ;; 550 | ms-excel) 551 | { can_do_office && cmd=(isoffice "$1" xls); } ;; 552 | ooffice1) 553 | { has_cmd sxw2txt && cmd=(istemp sxw2txt "$1"); } || 554 | { can_do_office && cmd=(isoffice "$1" odt); } ;; 555 | ipynb|epub) 556 | has_cmd pandoc && cmd=(pandoc -f "$x" -t plain "$1") ;; 557 | troff) 558 | fext=$(fileext "$1") 559 | declare macro=andoc 560 | [[ "$fext" == me ]] && macro=e 561 | [[ "$fext" == ms ]] && macro=s 562 | { has_cmd mandoc && cmd=(nodash mandoc "$1"); } || 563 | { has_cmd man && cmd=(man -l "$1"); } || 564 | { [[ $COLOR == *always ]] && has_cmd groff && cmd=(groff -s -p -t -e -Tutf8 -m "$macro" "$1"); } ;; 565 | rtf) 566 | { has_cmd unrtf && cmd=(istemp "unrtf --text" "$1"); } || 567 | { has_cmd libreoffice && cmd=(isoffice2 "$1"); } ;; 568 | dvi) 569 | has_cmd dvi2tty && cmd=(istemp "dvi2tty -q" "$1") ;; 570 | sharedlib) 571 | cmd=(istemp nm "$1") ;; 572 | pod) 573 | [[ -z $file2 ]] && 574 | { { has_cmd pod2text && cmd=(pod2text "$1"); } || 575 | { has_cmd perldoc && cmd=(istemp "perldoc -T" "$1"); }; } ;; 576 | hdf|hdf5) 577 | { has_cmd h5dump && cmd=(istemp h5dump "$1"); } || 578 | { has_cmd ncdump && cmd=(istemp ncdump "$1"); } ;; 579 | matlab) 580 | has_cmd matdump && cmd=(istemp "matdump -d" "$1") ;; 581 | djvu) 582 | has_cmd djvutxt && cmd=(djvutxt "$1") ;; 583 | x509|crl) 584 | has_cmd openssl && cmd=(istemp "openssl storeutl -text -noout" "$1") ;; 585 | csr) 586 | has_cmd openssl && cmd=(istemp "openssl req -text -noout -in" "$1") ;; 587 | pgp) 588 | has_cmd gpg && cmd=(gpg --decrypt --quiet --no-tty --batch --yes "$1") ;; 589 | bplist|plist) 590 | { has_cmd plistutil && cmd=(istemp "plistutil -i" "$1"); } || 591 | { has_cmd plutil && cmd=(istemp "plutil -p" "$1"); } ;; 592 | mp3) 593 | { has_cmd ffprobe && cmd=(ffprobe -hide_banner -- "$1"); } || 594 | { has_cmd eyeD3 && cmd=(istemp "eyeD3" "$1"); } || 595 | { has_cmd id3v2 && cmd=(istemp "id3v2 --list" "$1"); } ;; 596 | log) 597 | has_cmd ccze && [[ $COLOR = *always ]] && ccze -A < "$1" 598 | return ;; 599 | csv) 600 | msg "type -S for better display of very wide tables" 601 | { has_cmd csvtable && csvtable -h >/dev/null 2>&1 && cmd=(csvtable "$1"); } || 602 | { has_cmd csvlook && cmd=(csvlook -S "$1"); } || 603 | { has_cmd column && cmd=(istemp "column -s ,; -t" "$1"); } || 604 | { has_cmd pandoc && cmd=(pandoc -f csv -t plain "$1"); } ;; 605 | json) 606 | [[ $COLOR = *always ]] && opt=(-C .) || opt=(.) 607 | has_cmd jq && cmd=(jq "${opt[@]}" "$1") ;; 608 | zlib) 609 | has_cmd zlib-flate && zlib-flate -uncompress < "$1" && return ;; 610 | esac 611 | fi 612 | # not a specific file format 613 | if [[ -z ${cmd[*]} ]]; then 614 | fext=$(fileext "$1") 615 | if [[ $fcat == audio || $fcat == video || $fcat == image ]]; then 616 | { has_cmd ffprobe && [[ $fcat != image ]] && cmd=(ffprobe -hide_banner -- "$1"); } || 617 | { [[ "$1" != '-' ]] && has_cmd mediainfo && cmd=(mediainfo --Full "$1"); } || 618 | { has_cmd exiftool && cmd=(exiftool "$1"); } || 619 | { has_cmd identify && [[ $fcat == image ]] && cmd=(identify -verbose "$1"); } 620 | elif [[ "$fchar" == binary ]]; then 621 | cmd=(nodash strings "$1") 622 | fi 623 | fi 624 | if [[ -n ${cmd[*]} && "${cmd[*]}" != "cat" ]]; then 625 | [[ -z $msg ]] && msg="append $sep to filename to view the original $x file" 626 | msg "$msg" 627 | fi 628 | [[ -n "$file2" ]] && fext="$file2" 629 | [[ $fcat == text && $x != plain ]] && fext=$x 630 | [[ -z "$fext" ]] && fext=$(fileext "$fileext") 631 | fext=${fext##*/} 632 | [[ -z ${colorizer[*]} ]] && has_colorizer "$1" "$fext" "$fileext" 633 | if [[ -n ${cmd[*]} ]]; then 634 | # TAU: When cmd starts with environment variable settings, bash will refuse to execute it via : "${cmd[@]}" 635 | # The remedy is simple : Just run it through the "env" command in that case. 636 | [[ "$cmd" =~ '=' ]] && cmd=(env "${cmd[@]}") 637 | "${cmd[@]}" 638 | else 639 | [[ -n ${colorizer[*]} && $fcat != binary ]] && "${colorizer[@]}" && return 640 | # if fileext set, we need to filter to get rid of .fileext 641 | [[ -n $fileext || "$1" == - || "$1" == "$t" ]] && cat "$1" 642 | fi 643 | } 644 | 645 | isarchive () { 646 | prog=$1 647 | [[ "$2" =~ ^[a-z_-]*:.* ]] && echo "$2: remote operation tar host:file not allowed" && return 648 | if [[ -n $3 ]]; then 649 | case $prog in 650 | tar|bsdtar) 651 | [[ "$2" =~ ^[a-z_-]*:.* ]] && echo "$2: remote operation tar host:file not allowed" && return 652 | if [[ "$3" =~ .*/$ ]]; then 653 | $prog Otvf "$2" -- "$3" 654 | else 655 | $prog Oxf "$2" -- "$3" 2>/dev/null 656 | fi 657 | ;; 658 | rar|unrar) 659 | istemp "$prog p -inul" "$2" "$3" ;; 660 | ar) 661 | istemp "ar p" "$2" "$3" ;; 662 | unzip) 663 | istemp "unzip -avp" "$2" "$3" ;; 664 | cabextract) 665 | istemp cabextract2 "$2" "$3" ;; 666 | isoinfo) 667 | istemp "isoinfo -i" "$2" "-x$3" ;; 668 | cpio) 669 | if [[ "$2" == - ]]; then 670 | cpio -i --quiet --to-stdout "$3" 671 | else 672 | cpio -i --quiet --to-stdout --file "$2" "$3" 673 | fi ;; 674 | 7zz|7za|7zr) 675 | istemp "$prog e -so" "$2" "$3" 676 | esac 677 | else 678 | case $prog in 679 | tar|bsdtar) 680 | [[ "$2" =~ ^[a-z_-]*:.* ]] && echo "$2: remote operation tar host:file not allowed" && return 681 | $prog tvf "$2" ;; 682 | rar|unrar) 683 | istemp "$prog v" "$2" ;; 684 | ar) 685 | istemp "ar vt" "$2" ;; 686 | unzip) 687 | istemp "unzip -l" "$2" ;; 688 | cabextract) 689 | istemp "cabextract -l" "$2" ;; 690 | isoinfo) 691 | t="$2" 692 | istemp "isoinfo -d -i" "$2" 693 | isoinfo -d -i "$t"| grep -E '^Joliet' && joliet=J 694 | separatorline 695 | isoinfo -fR"$joliet" -i "$t" ;; 696 | cpio) 697 | cpio -tv --quiet < "$2" ;; 698 | 7zz|7za|7zr) 699 | istemp "$prog l" "$2" 700 | esac 701 | fi 702 | [[ $? != 0 && -n $3 ]] && msg ":$!: could not retrieve $3 from $2" 703 | } 704 | 705 | cabextract2 () { 706 | cabextract -pF "$2" "$1" 707 | } 708 | 709 | ispdf () { 710 | istemp pdftohtml -i -q -s -noframes -nodrm -stdout "$1"|ishtml - 711 | } 712 | 713 | isimage () { 714 | if [[ "$1" == appimage ]]; then 715 | offset="-o $("$2" --appimage-offset)" 716 | fi 717 | if [[ -z "$3" ]]; then 718 | [[ "$1" == snap ]] && has_cmd snap && snap info "$2" && separatorline 719 | istemp "unsquashfs -d . -llc $offset" "$2" 720 | else 721 | istemp "unsquashfs -cat $offset" "$2" "$3" 722 | fi 723 | } 724 | 725 | isrpm () { 726 | if [[ -z "$2" ]]; then 727 | if has_cmd rpm; then 728 | istemp "rpm -qivp --changelog --nomanifest --" "$1" 729 | separatorline 730 | [[ $1 == - ]] && set "$t" "$1" 731 | fi 732 | if has_cmd bsdtar; then 733 | bsdtar tvf "$1" 734 | else 735 | rpm2cpio "$1" 2>/dev/null|cpio -i -tv 2>/dev/null 736 | fi 737 | elif has_cmd bsdtar; then 738 | bsdtar xOf "$1" "$2" 739 | else 740 | rpm2cpio "$1" 2>/dev/null|cpio -i --quiet --to-stdout "$2" 741 | fi 742 | } 743 | 744 | isdeb () { 745 | if [[ "$1" = - ]]; then 746 | t=$(nexttmp) 747 | cat > "$t" 748 | set "$t" "$2" 749 | fi 750 | if has_cmd bsdtar; then 751 | data=$(bsdtar tf "$1" "data*") 752 | if [[ -z "$2" ]]; then 753 | control=$(bsdtar tf "$1" "control*") 754 | bsdtar xOf "$1" "$control" | bsdtar xOf - ./control 755 | separatorline 756 | bsdtar xOf "$1" "$data" | bsdtar tvf - 757 | else 758 | bsdtar xOf "$1" "$data" | bsdtar xOf - "$2" 759 | fi 760 | else 761 | if [[ "$1" = *@* ]]; then 762 | t=$(nexttmp) 763 | cat "$1" > "$t" 764 | set "$1" "$t" 765 | fi 766 | data=$(ar t "$1"|grep data) 767 | ft=$(ar p "$1" "$data" | filetype -) 768 | get_unpack_cmd "$ft" - 769 | if [[ -z "$2" ]]; then 770 | control=$(ar t "$1"|grep control) 771 | ar p "$1" "$control" | "${cmd[@]}" | tar xOf - ./control 772 | separatorline 773 | ar p "$1" "$data" | "${cmd[@]}" | tar tvf - 774 | else 775 | ar p "$1" "$data" | "${cmd[@]}" | tar xOf - "$2" 776 | fi 777 | fi 778 | } 779 | 780 | isoffice () { 781 | t=$(nexttmp) 782 | t2=$t."$2" 783 | cat "$1" > "$t2" 784 | libreoffice --headless --convert-to html --outdir "$tmpdir" "$t2" > /dev/null 2>&1 785 | ishtml "$t.html" 786 | } 787 | 788 | isoffice2 () { 789 | istemp "libreoffice --headless --cat" "$1" 2>/dev/null 790 | } 791 | 792 | isxmq () { 793 | [[ $COLOR == *always ]] && colarg="--color" || colarg="--mono" 794 | msg "xmq output, append :$2 to the filename to see the (colored) contents" 795 | xmq "$1" render-terminal "$colarg" 796 | } 797 | 798 | isdtb () { 799 | errors=$(nexttmp) 800 | has_colorizer "-" "dts" "dts" 801 | [[ -z "${colorizer[*]}" ]] && colorizer=(cat) 802 | dtc -I dtb -O dts -o - -- "$1" 2> "$errors" | "${colorizer[@]}" 803 | if [[ -s "$errors" ]]; then 804 | separatorline "Warnings" 805 | cat "$errors" 806 | fi 807 | } 808 | 809 | can_do_office () { 810 | if has_htmlprog && has_cmd libreoffice; then 811 | return 0 812 | fi 813 | return 1 814 | } 815 | 816 | has_htmlprog () { 817 | if has_cmd w3m || has_cmd lynx || has_cmd elinks || has_cmd html2text; then 818 | return 0 819 | fi 820 | return 1 821 | } 822 | 823 | handle_w3m () { 824 | if [[ "$1" == *\?* ]]; then 825 | t=$(nexttmp) 826 | ln -s "$1" "$t" 827 | set "$t" "$1" 828 | fi 829 | nodash "w3m -dump -T text/html" "$1" 830 | } 831 | 832 | ishtml () { 833 | [[ $1 == - ]] && arg1=-stdin || arg1="$1" 834 | htmlopt=--unicode-snob 835 | has_cmd html2text && html2text -utf8 /dev/null && htmlopt=-utf8 836 | # 3 lines following can easily be reshuffled according to the preferred tool 837 | has_cmd elinks && nodash "elinks -dump -force-html" "$1" && return || 838 | has_cmd w3m && handle_w3m "$1" && return || 839 | has_cmd lynx && lynx -force_html -dump "$arg1" && return || 840 | # different versions of html2text existingi, force unicode 841 | [[ "$1" == https://* ]] && return || 842 | has_cmd html2text && nodash html2text "$htmlopt" "$1" 843 | } 844 | 845 | # the main program 846 | set +o noclobber 847 | setopt sh_word_split 2>/dev/null 848 | PATH=$PATH:${0%%/lesspipe.sh} 849 | # the current locale in lowercase (or generic utf-8) 850 | charmap=$(locale -k charmap|tr '[:upper:]' '[:lower:]') || charmap="charmap=utf-8" 851 | eval "$charmap" 852 | 853 | sep=: # file name separator 854 | altsep='=' # alternate separator character 855 | if [[ -e "$1" && "$1" == *"$sep"* ]]; then 856 | sep=$altsep 857 | elif [[ "$1" == *"$altsep"* ]]; then 858 | [[ -e "${1%%"$altsep"*}" ]] && sep=$altsep 859 | fi 860 | 861 | tmpdir=${TMPDIR:-/tmp}/lesspipe."$RANDOM" 862 | [[ -d "$tmpdir" ]] || mkdir "$tmpdir" 863 | [[ -d "$tmpdir" ]] || exit 1 864 | trap 'rm -rf "$tmpdir";exit 1' INT 865 | trap 'rm -rf "$tmpdir"' EXIT 866 | trap - PIPE 867 | 868 | t=$(nexttmp) 869 | analyze_args 870 | # make LESSOPEN="|- ... " work 871 | if [[ $LESSOPEN == *\|-* && $1 == - ]]; then 872 | cat > "$t" 873 | [[ -n "$fext" ]] && t="$t$sep$fext" 874 | set "$1" "$t" 875 | nexttmp >/dev/null 876 | fi 877 | 878 | if [[ -z "$1" && "$0" == */lesspipe.sh ]]; then 879 | [[ "$0" == /* ]] || pat=$(pwd)/ 880 | if [[ "$SHELL" == *csh ]]; then 881 | echo "setenv LESSOPEN \"|$pat$0 %s\"" 882 | else 883 | echo "LESSOPEN=\"|$pat$0 %s\"" 884 | echo "export LESSOPEN" 885 | fi 886 | else 887 | [[ -x "${HOME}/.lessfilter" ]] && "${HOME}/.lessfilter" "$1" && exit 0 888 | if has_cmd lessfilter; then 889 | lessfilter "$1" && exit 0 890 | fi 891 | if [[ -z "$1" ]]; then 892 | LESSQUIET=1 893 | show - 894 | else 895 | show "$@" 896 | fi 897 | fi 898 | -------------------------------------------------------------------------------- /packaging/README: -------------------------------------------------------------------------------- 1 | The file lesspipe.spec is a template to create a lesspipe RPM package. 2 | After creating the required build directory structure, putting a 3 | lesspipe-xxx.tar.gz into the SOURCES and lesspipe.spec into the SPECS 4 | directories the RPM can be built by 5 | 6 | rpmbuild -ba SPECS/lesspipe.spec 7 | 8 | This template not copy the files INSTALL, README.md and the test suite, 9 | that could go into a lesspipe-dev package. 10 | 11 | Similarly the file control is a template for bulding a DEB package. The 12 | installed size is not the correct number. It would be nice to have 13 | complete building istructions here, but this requires the setup of 14 | a building environment and is not described here. 15 | -------------------------------------------------------------------------------- /packaging/control: -------------------------------------------------------------------------------- 1 | Package: lesspipe 2 | Version: 2.07-1 3 | Section: text 4 | Priority: optional 5 | Architecture: all 6 | Essential: no 7 | Depends: gzip 8 | Suggests: libreoffice, elinks, mediainfo, pdftotext 9 | Enhances: less 10 | Installed-Size: 263KB 11 | Homepage: https://www-zeuthen.desy.de/~friebel/unix/lesspipe.html 12 | Maintainer: Wolfgang Friebel 13 | Description: lesspipe.sh is an input filter for the pager less. 14 | lesspipe.sh is a sophisticated version to enhance less. It is able to process 15 | a wide variety of file formats. It enables users to deeply inspect archives 16 | and to display the contents of files in archives without having to unpack 17 | them before. That means file contents can be properly interpreted even if 18 | the files are compressed and contained in a hierarchy of archives (often 19 | found in RPM or DEB archives containing source tarballs). The filter is 20 | easily extensible for new formats. The input filter is a bash script, but 21 | works as well as a zsh script. For zsh and bash a completion mechanism for 22 | archive contents is provided. 23 | -------------------------------------------------------------------------------- /packaging/lesspipe.spec: -------------------------------------------------------------------------------- 1 | %define packagename lesspipe 2 | %define packageversion 2.17 3 | %define packagerelease 1 4 | 5 | Name: %{packagename} 6 | Version: %{packageversion} 7 | Release: %{packagerelease} 8 | Group: Languages 9 | Source0: lesspipe-%{packageversion}.tar.gz 10 | BuildArch: noarch 11 | AutoReqProv: on 12 | Packager: Wolfgang Friebel 13 | URL: https://github.com/wofr06/lesspipe.sh/archive/lesspipe.zip 14 | License: GPL 15 | BuildRoot: /var/tmp/%{packagename}-%{packageversion} 16 | Summary: Input filter for less to better display files 17 | 18 | %description 19 | lesspipe.sh is an input filter for the pager less. It is able to process a 20 | wide variety of file formats. It enables users to deeply inspect archives 21 | and to display the contents of files in archives without having to unpack 22 | them before. That means file contents can be properly interpreted even if 23 | the files are compressed and contained in a hierarchy of archives (often 24 | found in RPM or DEB archives containing source tarballs). The filter is 25 | easily extensible for new formats. The input filter is a bash script, but 26 | works as well as a zsh script. For zsh and bash tab completion mechanisms 27 | for archive contents are provided. 28 | 29 | %prep 30 | %setup -n lesspipe-%{packageversion} 31 | 32 | %build 33 | 34 | %define prefix /usr/local 35 | ./configure --prefix=$RPM_BUILD_ROOT%{prefix} 36 | 37 | %install 38 | # 39 | # after some safety checks, clean out the build root 40 | # 41 | [ ! -z "$RPM_BUILD_ROOT" ] && [ "$RPM_BUILD_ROOT" != "/" ] && \ 42 | rm -rf $RPM_BUILD_ROOT 43 | 44 | #run install script first so we can pick up all of the files 45 | 46 | make install 47 | 48 | # create profile.d scripts to set LESSOPEN 49 | mkdir -p $RPM_BUILD_ROOT/etc/profile.d 50 | cat << EOF > $RPM_BUILD_ROOT/etc/profile.d/zzless.sh 51 | [ -x %{prefix}/bin/lesspipe.sh ] && export LESSOPEN="|%{prefix}/bin/lesspipe.sh %s" 52 | EOF 53 | 54 | %clean 55 | 56 | cd $RPM_BUILD_DIR 57 | [ ! -z "$RPM_BUILD_ROOT" ] && [ "$RPM_BUILD_ROOT" != "/" ] && \ 58 | rm -rf $RPM_BUILD_ROOT 59 | [ ! -z "$RPM_BUILD_DIR" ] && [ "$RPM_BUILD_DIR" != "/" ] && \ 60 | rm -rf $RPM_BUILD_DIR/lesspipe-%{packageversion} 61 | 62 | %pre 63 | 64 | %post 65 | 66 | %preun 67 | 68 | %postun 69 | 70 | %files 71 | 72 | %defattr(-,root,root) 73 | %{prefix}/bin/lesspipe.sh 74 | %{prefix}/bin/archive_color 75 | %{prefix}/bin/code2color 76 | %{prefix}/bin/vimcolor 77 | %{prefix}/bin/sxw2txt 78 | %{prefix}/bin/lesscomplete 79 | %{prefix}/share/man/man1/* 80 | %{prefix}/share/zsh/site-functions 81 | %{prefix}/share/bash-completion 82 | /etc/profile.d/* 83 | 84 | %docdir %{prefix}/share/man/man1 85 | 86 | %changelog 87 | * Sun Feb 16 2025 2.18-1 - wp.friebel@gmail.com 88 | - documentation enhanced, better xlsx support 89 | * Sun Dec 22 2024 2.17-1 - wp.friebel@gmail.com 90 | - Fixes for xslx and MacOS 91 | * Sun Nov 10 2024 2.16-1 - wp.friebel@gmail.com 92 | - file name checks for ar 93 | * Thu Oct 03 2024 2.15-1 - wp.friebel@gmail.com 94 | - display all certificates in pem files 95 | * Fri Aug 16 2024 2.14-1 - wp.friebel@gmail.com 96 | - prefer nvimpager for coloring if installed 97 | * Fri May 10 2024 2.13-1 - wp.friebel@gmail.com 98 | - support appimage and snap files 99 | * Mon Mar 18 2024 2.12-1 - wp.friebel@gmail.com 100 | - improved completion mechanism 101 | * Wed Dec 13 2023 2.11-1 - wp.friebel@gmail.com 102 | - changed output for csv files 103 | * Tue Oct 05 2023 2.10-1 - wp.friebel@gmail.com 104 | - added zlib support, recognize jsx and tsx, view csv files using column 105 | * Mon Jun 26 2023 2.08-1 - wp.friebel@gmail.com 106 | - improved coloring output, support for device tree blob files, bug fixes 107 | * Sun Jan 08 2023 2.07-1 - wp.friebel@gmail.com 108 | - support json, mail archives, update man page, other bat/batcat defaults 109 | * Wed Aug 17 2022 2.06-1 20220817 - wp.friebel@gmail.com 110 | - remove perl storable files handling, changes recommended by Shellcheck 111 | * Tue Apr 26 2022 2.05-1 20220426 - wp.friebel@gmail.com 112 | - fix colorizing using bat and for file names containing spaces 113 | * Mon Feb 28 2022 2.04-1 20220228 - wp.friebel@gmail.com 114 | - handle csv files, lessfilter can be in path 115 | * Tue Feb 22 2022 2.03-1 20220222 - wp.friebel@gmail.com 116 | - better handling of colorizing, improved code2color 117 | * Wed Jan 19 2022 2.02-1 20220119 - wp.friebel@gmail.com 118 | - add .lessfilter support, fixes for html and rpm handling 119 | * Tue Jan 04 2022 2.01-1 20220104 - wp.friebel@gmail.com 120 | - added zsh completion mechanism for archive contents 121 | * Tue Dec 28 2021 2.00-1 20211228 - wp.friebel@gmail.com 122 | - heavily rewritten version 123 | * Tue Jul 28 2015 1.83-1 20150728 - Wolfgang.Friebel@desy.de 124 | - new version (see ChangeLog) 125 | * Mon Feb 04 2013 1.82-1 20130204 - Wolfgang.Friebel@desy.de 126 | - protect against iconv errors 127 | * Mon Jan 14 2013 1.81-1 20130114 - Wolfgang.Friebel@desy.de 128 | - initial build starting with (prerelease of) lesspipe version 1.81 129 | -------------------------------------------------------------------------------- /sxw2txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # sxw2txt -- Converts OpenOffice.org Writer files to plain text. 4 | # Copyright (C) 2004 Liam Morland 5 | # Copyright (C) 2006 Vincent Lefevre 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU General Public License 9 | # as published by the Free Software Foundation; either version 2 10 | # of the License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 20 | # USA. 21 | # 22 | # Liam Morland 23 | # 86A McDougall Road, Waterloo, Ontario, N2L 5C5, CANADA 24 | # 25 | # Changes by Vincent Lefevre . 26 | # 2006-03-21: Better #! line, typos, error messages output to stderr, 27 | # better whitespace removal, clean execution of unzip (for 28 | # filenames with special characters). 29 | # 2006-03-22: Use Archive::Zip, otherwise fallback to the unzip command. 30 | # 2006-03-26: Conversions from Symbol aka "Standard Symbols L" characters 31 | # (in the private Unicode area U+F020..U+F0FF) into standard 32 | # Unicode characters. I don't know whether this private area 33 | # is used in a consistent way by OpenOffice, but IMHO, this 34 | # conversion is better than nothing. 35 | 36 | use strict; 37 | use warnings; 38 | 39 | my ($proc) = '$Id: sxw2txt 11666 2006-03-26 22:16:17Z lefevre $' 40 | =~ /^.Id: (\S+) / or die; 41 | 42 | # First argument is taken to be the input file. All other args are ignored. 43 | my $input_file = shift; 44 | 45 | # If we have a filename, try to get the content.xml from it, 46 | # otherwise print usage information. 47 | defined $input_file or $! = 1, die <new(); 55 | }; 56 | 57 | if (defined $zip) 58 | { 59 | $zip->read($input_file) 60 | and die "$proc: $input_file is unreadable or has an incorrect format\n"; 61 | $_ = $zip->contents('content.xml') 62 | or die "$proc: can't extract content.xml from $input_file\n"; 63 | } 64 | else 65 | { 66 | my $pid = open UNZIP, "-|"; 67 | defined $pid or die "$proc: can't fork: $!\n"; 68 | unless ($pid) # child 69 | { 70 | # It's probably better to get the possible error message from unzip, 71 | # to know the reason of the failure (e.g. "Permission denied"). 72 | #open STDERR, '/dev/null'; 73 | exec 'unzip', '-p', $input_file, 'content.xml'; 74 | die "$proc: exec unzip failed: $!\n"; 75 | } 76 | $_ = do { local $/; }; 77 | close UNZIP or die "$proc: can't extract content.xml from $input_file\n"; 78 | } 79 | 80 | # Convert the OOo XML to text with a series of regex substitutions. 81 | s,\n+, ,g; 82 | 83 | # Tables are wrapped with [begin-table] and [end-table]. 84 | # Rows and cells begin with [table-row] and [table-cell] respectively. 85 | s,]*)?>,\n\n[begin-table],g; 86 | s,,\n[end-table],g; 87 | s,]*)?>(<[^>]+>)*]*>,\n[table cell],g; 88 | s,]*)?>,\n\n[table row],g; 89 | 90 | # OOo tabs are made into tab characters. 91 | s,,\t,g; 92 | 93 | # Each list item is given a '*' as a bullet. 94 | # Sorry, no fancy support for nested lists yet. 95 | s,]*>,\n\n* ,g; 96 | 97 | # Skip two lines before each new paragraph. 98 | s,]*>,\n\n,g; 99 | 100 | # Get rid of any remaining tags. Want to add support for tags not 101 | # handled above? Do it above this line. 102 | s,<[^>]*>,,g; 103 | 104 | # Convert common entities into the appropriate character. 105 | s,<,<,g; 106 | s,>,>,g; 107 | s,',',g; 108 | s,",",g; 109 | s,&,&,g; 110 | 111 | # Convert "Standard Symbols L" characters into standard Unicode 112 | # characters. Note: I could have written 113 | # binmode UNZIP, ":utf8"; 114 | # just before reading UNZIP to use Unicode code points directly, 115 | # but this makes the substitutions very slow. 116 | my %symbol = 117 | ( 118 | "\x{80}\x{A1}" => "!", 119 | "\x{80}\x{A2}" => "\x{E2}\x{88}\x{80}", 120 | "\x{80}\x{A3}" => "#", 121 | "\x{80}\x{A4}" => "\x{E2}\x{88}\x{83}", 122 | "\x{80}\x{A5}" => "%", 123 | "\x{80}\x{A6}" => "&", 124 | "\x{80}\x{A7}" => "\x{C9}\x{9C}", 125 | "\x{80}\x{A8}" => "(", 126 | "\x{80}\x{A9}" => ")", 127 | "\x{80}\x{AA}" => "*", 128 | "\x{80}\x{AB}" => "+", 129 | "\x{80}\x{AC}" => ",", 130 | "\x{80}\x{AD}" => "-", 131 | "\x{80}\x{AE}" => ".", 132 | "\x{80}\x{AF}" => "/", 133 | "\x{80}\x{B0}" => "0", 134 | "\x{80}\x{B1}" => "1", 135 | "\x{80}\x{B2}" => "2", 136 | "\x{80}\x{B3}" => "3", 137 | "\x{80}\x{B4}" => "4", 138 | "\x{80}\x{B5}" => "5", 139 | "\x{80}\x{B6}" => "6", 140 | "\x{80}\x{B7}" => "7", 141 | "\x{80}\x{B8}" => "8", 142 | "\x{80}\x{B9}" => "9", 143 | "\x{80}\x{BA}" => ":", 144 | "\x{80}\x{BB}" => ";", 145 | "\x{80}\x{BC}" => "<", 146 | "\x{80}\x{BD}" => "=", 147 | "\x{80}\x{BE}" => ">", 148 | "\x{80}\x{BF}" => "?", 149 | "\x{81}\x{80}" => "\x{E2}\x{89}\x{85}", 150 | "\x{81}\x{81}" => "\x{CE}\x{91}", 151 | "\x{81}\x{82}" => "\x{CE}\x{92}", 152 | "\x{81}\x{83}" => "\x{CE}\x{A7}", 153 | "\x{81}\x{84}" => "\x{CE}\x{94}", 154 | "\x{81}\x{85}" => "\x{CE}\x{95}", 155 | "\x{81}\x{86}" => "\x{CE}\x{A6}", 156 | "\x{81}\x{87}" => "\x{CE}\x{93}", 157 | "\x{81}\x{88}" => "\x{CE}\x{97}", 158 | "\x{81}\x{89}" => "\x{CE}\x{99}", 159 | "\x{81}\x{8A}" => "\x{CF}\x{91}", 160 | "\x{81}\x{8B}" => "\x{CE}\x{9A}", 161 | "\x{81}\x{8C}" => "\x{CE}\x{9B}", 162 | "\x{81}\x{8D}" => "\x{CE}\x{9C}", 163 | "\x{81}\x{8E}" => "\x{CE}\x{9D}", 164 | "\x{81}\x{8F}" => "\x{CE}\x{9F}", 165 | "\x{81}\x{90}" => "\x{CE}\x{A0}", 166 | "\x{81}\x{91}" => "\x{CE}\x{98}", 167 | "\x{81}\x{92}" => "\x{CE}\x{A1}", 168 | "\x{81}\x{93}" => "\x{CE}\x{A3}", 169 | "\x{81}\x{94}" => "\x{CE}\x{A4}", 170 | "\x{81}\x{95}" => "\x{CE}\x{A5}", 171 | "\x{81}\x{96}" => "\x{CF}\x{82}", 172 | "\x{81}\x{97}" => "\x{CE}\x{A9}", 173 | "\x{81}\x{98}" => "\x{CE}\x{9E}", 174 | "\x{81}\x{99}" => "\x{CE}\x{A8}", 175 | "\x{81}\x{9A}" => "\x{CE}\x{96}", 176 | "\x{81}\x{9B}" => "[", 177 | "\x{81}\x{9C}" => "\x{E2}\x{88}\x{B4}", 178 | "\x{81}\x{9D}" => "]", 179 | "\x{81}\x{9E}" => "\x{E2}\x{8A}\x{A5}", 180 | "\x{81}\x{9F}" => "_", 181 | "\x{81}\x{A0}" => "\x{C2}\x{AF}", 182 | "\x{81}\x{A1}" => "\x{CE}\x{B1}", 183 | "\x{81}\x{A2}" => "\x{CE}\x{B2}", 184 | "\x{81}\x{A3}" => "\x{CF}\x{87}", 185 | "\x{81}\x{A4}" => "\x{CE}\x{B4}", 186 | "\x{81}\x{A5}" => "\x{CE}\x{B5}", 187 | "\x{81}\x{A6}" => "\x{CF}\x{95}", 188 | "\x{81}\x{A7}" => "\x{CE}\x{B3}", 189 | "\x{81}\x{A8}" => "\x{CE}\x{B7}", 190 | "\x{81}\x{A9}" => "\x{CE}\x{B9}", 191 | "\x{81}\x{AA}" => "\x{CF}\x{86}", 192 | "\x{81}\x{AB}" => "\x{CE}\x{BA}", 193 | "\x{81}\x{AC}" => "\x{CE}\x{BB}", 194 | "\x{81}\x{AD}" => "\x{CE}\x{BC}", 195 | "\x{81}\x{AE}" => "\x{CE}\x{BD}", 196 | "\x{81}\x{AF}" => "\x{CE}\x{BF}", 197 | "\x{81}\x{B0}" => "\x{CF}\x{80}", 198 | "\x{81}\x{B1}" => "\x{CE}\x{B8}", 199 | "\x{81}\x{B2}" => "\x{CF}\x{81}", 200 | "\x{81}\x{B3}" => "\x{CF}\x{83}", 201 | "\x{81}\x{B4}" => "\x{CF}\x{84}", 202 | "\x{81}\x{B5}" => "\x{CF}\x{85}", 203 | "\x{81}\x{B6}" => "\x{CF}\x{96}", 204 | "\x{81}\x{B7}" => "\x{CF}\x{89}", 205 | "\x{81}\x{B8}" => "\x{CE}\x{BE}", 206 | "\x{81}\x{B9}" => "\x{CF}\x{88}", 207 | "\x{81}\x{BA}" => "\x{CE}\x{B6}", 208 | "\x{81}\x{BB}" => "{", 209 | "\x{81}\x{BC}" => "|", 210 | "\x{81}\x{BD}" => "}", 211 | "\x{81}\x{BE}" => "~", 212 | "\x{82}\x{A1}" => "\x{CF}\x{92}", 213 | "\x{82}\x{A2}" => "\x{E2}\x{80}\x{B2}", 214 | "\x{82}\x{A3}" => "\x{E2}\x{89}\x{A4}", 215 | "\x{82}\x{A4}" => "/", 216 | "\x{82}\x{A5}" => "\x{E2}\x{88}\x{9E}", 217 | "\x{82}\x{A6}" => "f", 218 | "\x{82}\x{A7}" => "\x{E2}\x{99}\x{A3}", 219 | "\x{82}\x{A8}" => "\x{E2}\x{99}\x{A6}", 220 | "\x{82}\x{A9}" => "\x{E2}\x{99}\x{A5}", 221 | "\x{82}\x{AA}" => "\x{E2}\x{99}\x{A0}", 222 | "\x{82}\x{AB}" => "\x{E2}\x{86}\x{94}", 223 | "\x{82}\x{AC}" => "\x{E2}\x{86}\x{90}", 224 | "\x{82}\x{AD}" => "\x{E2}\x{86}\x{91}", 225 | "\x{82}\x{AE}" => "\x{E2}\x{86}\x{92}", 226 | "\x{82}\x{AF}" => "\x{E2}\x{86}\x{93}", 227 | "\x{82}\x{B0}" => "\x{C2}\x{B0}", 228 | "\x{82}\x{B1}" => "\x{C2}\x{B1}", 229 | "\x{82}\x{B2}" => "\x{E2}\x{80}\x{B3}", 230 | "\x{82}\x{B3}" => "\x{E2}\x{89}\x{A5}", 231 | "\x{82}\x{B4}" => "\x{C3}\x{97}", 232 | "\x{82}\x{B5}" => "\x{E2}\x{88}\x{9D}", 233 | "\x{82}\x{B6}" => "\x{E2}\x{88}\x{82}", 234 | "\x{82}\x{B7}" => "\x{E2}\x{80}\x{A2}", 235 | "\x{82}\x{B8}" => "\x{C3}\x{B7}", 236 | "\x{82}\x{B9}" => "\x{E2}\x{89}\x{A0}", 237 | "\x{82}\x{BA}" => "\x{E2}\x{89}\x{A1}", 238 | "\x{82}\x{BB}" => "\x{E2}\x{89}\x{88}", 239 | "\x{82}\x{BC}" => "\x{E2}\x{80}\x{A6}", 240 | "\x{82}\x{BD}" => "|", 241 | "\x{82}\x{BE}" => "\x{E2}\x{80}\x{94}", 242 | "\x{82}\x{BF}" => "\x{E2}\x{86}\x{B5}", 243 | "\x{83}\x{80}" => "\x{E2}\x{84}\x{B5}", 244 | "\x{83}\x{81}" => "\x{E2}\x{84}\x{91}", 245 | "\x{83}\x{82}" => "\x{E2}\x{84}\x{9C}", 246 | "\x{83}\x{83}" => "\x{E2}\x{84}\x{98}", 247 | "\x{83}\x{84}" => "\x{E2}\x{8A}\x{97}", 248 | "\x{83}\x{85}" => "\x{E2}\x{8A}\x{95}", 249 | "\x{83}\x{86}" => "\x{E2}\x{88}\x{85}", 250 | "\x{83}\x{87}" => "\x{E2}\x{88}\x{A9}", 251 | "\x{83}\x{88}" => "\x{E2}\x{88}\x{AA}", 252 | "\x{83}\x{89}" => "\x{E2}\x{8A}\x{83}", 253 | "\x{83}\x{8A}" => "\x{E2}\x{8A}\x{87}", 254 | "\x{83}\x{8B}" => "\x{E2}\x{8A}\x{84}", 255 | "\x{83}\x{8C}" => "\x{E2}\x{8A}\x{82}", 256 | "\x{83}\x{8D}" => "\x{E2}\x{8A}\x{86}", 257 | "\x{83}\x{8E}" => "\x{E2}\x{88}\x{88}", 258 | "\x{83}\x{8F}" => "\x{E2}\x{88}\x{89}", 259 | "\x{83}\x{90}" => "\x{E2}\x{88}\x{A0}", 260 | "\x{83}\x{91}" => "\x{E2}\x{88}\x{87}", 261 | "\x{83}\x{92}" => "\x{C2}\x{AE}", 262 | "\x{83}\x{93}" => "\x{C2}\x{A9}", 263 | "\x{83}\x{94}" => "\x{E2}\x{84}\x{A2}", 264 | "\x{83}\x{95}" => "\x{E2}\x{88}\x{8F}", 265 | "\x{83}\x{96}" => "\x{E2}\x{88}\x{9A}", 266 | "\x{83}\x{97}" => "\x{E2}\x{88}\x{99}", 267 | "\x{83}\x{98}" => "\x{C2}\x{AC}", 268 | "\x{83}\x{99}" => "\x{E2}\x{88}\x{A7}", 269 | "\x{83}\x{9A}" => "\x{E2}\x{88}\x{A8}", 270 | "\x{83}\x{9B}" => "\x{E2}\x{87}\x{94}", 271 | "\x{83}\x{9C}" => "\x{E2}\x{87}\x{90}", 272 | "\x{83}\x{9D}" => "\x{E2}\x{87}\x{91}", 273 | "\x{83}\x{9E}" => "\x{E2}\x{87}\x{92}", 274 | "\x{83}\x{9F}" => "\x{E2}\x{87}\x{93}", 275 | "\x{83}\x{A0}" => "\x{E2}\x{8B}\x{84}", 276 | "\x{83}\x{A1}" => "\x{E3}\x{80}\x{88}", 277 | "\x{83}\x{A2}" => "\x{C2}\x{AE}", 278 | "\x{83}\x{A3}" => "\x{C2}\x{A9}", 279 | "\x{83}\x{A4}" => "\x{E2}\x{84}\x{A2}", 280 | "\x{83}\x{A5}" => "\x{E2}\x{88}\x{91}", 281 | "\x{83}\x{A6}" => "\x{E2}\x{8E}\x{9B}", 282 | "\x{83}\x{A7}" => "\x{E2}\x{8E}\x{9C}", 283 | "\x{83}\x{A8}" => "\x{E2}\x{8E}\x{9D}", 284 | "\x{83}\x{A9}" => "\x{E2}\x{8E}\x{A1}", 285 | "\x{83}\x{AA}" => "\x{E2}\x{8E}\x{A2}", 286 | "\x{83}\x{AB}" => "\x{E2}\x{8E}\x{A3}", 287 | "\x{83}\x{AC}" => "\x{E2}\x{8E}\x{A7}", 288 | "\x{83}\x{AD}" => "\x{E2}\x{8E}\x{A8}", 289 | "\x{83}\x{AE}" => "\x{E2}\x{8E}\x{A9}", 290 | "\x{83}\x{AF}" => "\x{E2}\x{8E}\x{AA}", 291 | "\x{83}\x{B1}" => "\x{E3}\x{80}\x{89}", 292 | "\x{83}\x{B2}" => "\x{E2}\x{88}\x{AB}", 293 | "\x{83}\x{B3}" => "\x{E2}\x{8C}\x{A0}", 294 | "\x{83}\x{B4}" => "\x{E2}\x{8E}\x{AE}", 295 | "\x{83}\x{B5}" => "\x{E2}\x{8C}\x{A1}", 296 | "\x{83}\x{B6}" => "\x{E2}\x{8E}\x{9E}", 297 | "\x{83}\x{B7}" => "\x{E2}\x{8E}\x{9F}", 298 | "\x{83}\x{B8}" => "\x{E2}\x{8E}\x{A0}", 299 | "\x{83}\x{B9}" => "\x{E2}\x{8E}\x{A4}", 300 | "\x{83}\x{BA}" => "\x{E2}\x{8E}\x{A5}", 301 | "\x{83}\x{BB}" => "\x{E2}\x{8E}\x{A6}", 302 | "\x{83}\x{BC}" => "\x{E2}\x{8E}\x{AB}", 303 | "\x{83}\x{BD}" => "\x{E2}\x{8E}\x{AC}", 304 | "\x{83}\x{BE}" => "\x{E2}\x{8E}\x{AD}", 305 | ); 306 | s,\x{EF}(..),$symbol{$1},eg; 307 | 308 | # Remove extra whitespace and print the result, always ending with \n. 309 | s/\n{3,}/\n\n/sg; 310 | s/[^\S\n]*\n/\n/g; 311 | s,^\s*(.*?)\s*$,$1,s; 312 | print "$_\n"; 313 | -------------------------------------------------------------------------------- /test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use Term::ANSIColor; 5 | use File::Temp;# qw(tempdir); 6 | use File::Copy; 7 | use Archive::Tar; 8 | 9 | # do a clean up if we get a CTRL-C 10 | our $tdir; 11 | $SIG{INT} = sub { if ($tdir) {unlink $tdir; print "\n"; exit 1 }}; 12 | 13 | sub usage { 14 | print <newdir("$tmp/lesspipeXXXX"); 81 | mkdir "$tdir/tests"; 82 | my $T="$tdir/tests"; 83 | copy("tests/archive.tgz","$T/archive.tgz") or die "$!"; 84 | copy("tests/compress.tgz","$T/compress.tgz") or die "$!"; 85 | copy("tests/filter.tgz","$T/filter.tgz") or die "$!"; 86 | copy("tests/special.tgz","$T/special.tgz") or die "$!"; 87 | my $cwd = $ENV{PWD}; 88 | chdir $T; 89 | my $tar = Archive::Tar->new; 90 | for my $arch (qw(archive compress filter special)) { 91 | my $next = Archive::Tar->iter("$arch.tgz", 1); 92 | while( my $f = $next->() ) { 93 | $f->extract or warn "Extraction failed"; 94 | } 95 | } 96 | symlink 'test_plain', 'symlink'; 97 | chdir $cwd; 98 | 99 | while () { 100 | last if /^END\n$/; 101 | print if /^###/ and ! @numtest and ! @strtest; 102 | next if /^#|^\s*$/; 103 | $num = $1 if s/^(\d+)\s+//; 104 | if (! /^less\s|\|\s*less|^LESS|\|\s*LESS.*less/) { 105 | print "### skipping invalid line $_"; 106 | next; 107 | } 108 | my $cmd = $_; 109 | chomp $cmd; 110 | $cmd =~ s/\$T/$tdir/g; 111 | my $comp = ; 112 | my $skip; 113 | $skip = 1 if @numtest and ! grep {$num == $_} @numtest; 114 | $skip = 1 if @strtest and ! grep {$cmd =~ /$_/} @strtest; 115 | $skip = 1 if ! $num; 116 | next if $skip; 117 | $comment = $cmd =~ s/\s+#(.*)// ? $1 : ''; 118 | $needed = $comment =~ s/[#,]? needs (.*)// ? $1 : ''; 119 | $needed =~ s/ or /|/g; 120 | $needed =~ s/ not /!/g; 121 | $needed =~ s/ and /,/g; 122 | $needed =~ s/html_converter/w3m|lynx|elinks|html2text/; 123 | my @needed = split /\s*\|\s*/, $needed; 124 | if ($noaction) { 125 | my $needed_str = $needed ? " ($needed)" : ''; 126 | print $verbose ? "$num $cmd $needed_str\n" : "$num $cmd\n"; 127 | next; 128 | } 129 | my $ignore = 0; 130 | $ignore = 1 if @needed; 131 | for my $andargs (@needed) { 132 | my $good = 1; 133 | for (split /\s*,\s*/, $andargs) { 134 | if (s/^!//) { 135 | $good = 0 if ! is_not_exec($_); 136 | } else { 137 | $good = 0 if is_not_exec($_); 138 | } 139 | } 140 | $ignore = 0 if $good; 141 | } 142 | if ($comp =~ /^c/ and $comment !~ /directory/ and $ENV{LESSCOLORIZER} 143 | and ! grep {$ENV{LESSCOLORIZER} =~ /^$_\b/} 144 | qw(bat batcat pygmentize source-highlight code2color vimcolor)){ 145 | $ignore = 1; 146 | $needed = 'a colorizer'; 147 | } 148 | my $res = $ignore ? '' : `$cmd 2>&1`; 149 | my $ok = 0; 150 | my $lines = 0; 151 | # zsh|bash|ksh style|file not found 152 | if ($res =~ /command not found: \S+|\S+:\s+command not found|\S+:\s+not found|no such file or directory: .*?[^\/]+\b$/m) { 153 | $res = "NOT found: " . $res; 154 | $ok = 1; 155 | } else { 156 | if ($ignore) { 157 | $sumignore++; 158 | } else { 159 | $ok = comp($res, $comp); 160 | if ($ok) { 161 | $sumok++; 162 | } else { 163 | $sumnok++; 164 | } 165 | } 166 | } 167 | print "result for :$cmd:\n$res" if $ok and $verbose; 168 | printf "%2d %6s %s %s\n", $num, $ignore ? 'ignore' : $ok ? $ok: 'NOT ok', $comment, $ignore ? "(needs $needed)" : ''; 169 | print "\t failing command: $cmd\n" if ! $ok and ! $ignore; 170 | $num = 0; 171 | } 172 | 173 | $duration = time() - $duration; 174 | print "$sumok/$sumignore/$sumnok tests passed/ignored/failed in $duration seconds\n" if ! $noaction; 175 | exit $sumnok; 176 | 177 | sub is_not_exec { 178 | my $arg = shift; 179 | return 0 if ! $arg; 180 | for my $prog (split ' ', $arg) { 181 | return 1 if ! grep {-x "$_/$prog"} split /:/, $ENV{PATH}; 182 | } 183 | return undef; 184 | } 185 | 186 | sub comp { 187 | my ($res, $comp) = @_; 188 | chomp $comp; 189 | my $ok = ''; 190 | my $reset = color('reset'); 191 | # ignore unicode start of file 192 | $res =~ s/^\x{fe}\x{ff}//; 193 | $res =~ s/^\x{ef}\x{bb}\x{bf}//; 194 | if ($comp =~ /^= ?/) { 195 | $comp =~ s/^= ?//; 196 | # ignore leading and trailing newlines 197 | $res =~ s/^\n//g; 198 | $res =~ s/\0//g; 199 | $res =~ s/\014//g; 200 | $res =~ s/\r?\n$//g; 201 | return 'ok' if $res eq $comp; 202 | for (split /\|/, $comp) { 203 | return 'ok' if $res eq $_; 204 | } 205 | for (split /\|/, $comp) { 206 | print ":$res:\ndiffers from\n:$_:\n" if $errors; 207 | } 208 | } elsif ($comp =~ s/^~ //) { 209 | return 'ok' if $res =~ /^$comp\r?/m; 210 | print ":$res:\ndoes not match\n:$comp:\n" if $errors; 211 | } elsif ($comp =~ s/^c //) { 212 | $ok = (grep {s/.*(\e\S+)$comp\b.*/$1ok$reset/} split /\n/, $res)[0]; 213 | $ok =~ s/[()-]//g if $ok; 214 | print ":$res:\ndoes not match\n:$comp:\n" if ! $ok and $errors; 215 | } else { 216 | print "unknown test (must start with c ~ or =): $comp\n"; 217 | } 218 | return $ok; 219 | } 220 | __END__ 221 | ### archive tests 222 | 1 less tests/archive.tgz # contents of archive with test files 223 | ~ .* test_tar 224 | 2 less $T/tests/test_tar # tar contents (from unpacked file) 225 | ~ .* tests/textfile 226 | 3 less tests/archive.tgz:test_tar # tar contents (from archive without unpacking) 227 | ~ .* tests/textfile 228 | 4 less $T/tests/test_tar:tests/textfile # extract file from tar (unpacked) 229 | = test 230 | 5 less tests/archive.tgz:test_tar:tests/textfile # (on the fly) 231 | = test 232 | ### plain tar file names with a : not allowed, use ./tar:name, not tar:name 233 | 6 less $T/tests/test:tar # tar file name with colon git #51 234 | ~ .* tests/textfile 235 | 7 less $T/tests/test:tar=tests/textfile # extract file from tar file with colon 236 | = test 237 | 8 less $T/tests/test_rpm # rpm contents, needs rpm2cpio 238 | ~ .* ./textfile 239 | 9 less tests/archive.tgz:test_rpm # (on the fly), needs rpm2cpio 240 | ~ .* ./textfile 241 | 10 less $T/tests/test_rpm:./textfile # extract file from rpm, needs rpm2cpio 242 | = test 243 | 11 less tests/archive.tgz:test_rpm:./textfile # (on the fly), needs rpm2cpio 244 | = test 245 | 12 less $T/tests/test.jar # jar contents, needs unzip 246 | ~ .*/MANIFEST.MF 247 | 13 less tests/archive.tgz:test.jar # (on the fly), needs unzip 248 | ~ .*/MANIFEST.MF 249 | 14 less $T/tests/test.jar:META-INF/MANIFEST.MF # # extract file from jar, needs unzip 250 | ~ .*: test 251 | 15 less tests/archive.tgz:test.jar:META-INF/MANIFEST.MF # (on the fly), needs unzip 252 | ~ .*: test 253 | 16 less $T/tests/test_zip # zip contents, needs unzip 254 | ~ .* 10240 .* 255 | 17 less tests/archive.tgz:test_zip # (on the fly), needs unzip 256 | ~ .* 10240 .* 257 | 18 less $T/tests/test_zip:tests/test.tar # extract tar archive from zip, needs unzip 258 | ~ .* tests/textfile 259 | 19 less tests/archive.tgz:test_zip:tests/test.tar # (on the fly), needs unzip 260 | ~ .* tests/textfile 261 | 20 less $T/tests/test_zip:tests/test.tar:tests/textfile # extract file from chained archives git #45, needs unzip 262 | = test 263 | 21 less tests/archive.tgz:test_zip:tests/test.tar:tests/textfile # (on the fly), needs unzip 264 | = test 265 | 22 less $T/tests/test_deb # debian contents 266 | ~ .* ./test.txt 267 | 23 less tests/archive.tgz:test_deb # (on the fly) 268 | ~ .* ./test.txt 269 | 24 less $T/tests/test_deb:./test.txt # extract file from debian package 270 | = test 271 | 25 less tests/archive.tgz:test_deb:./test.txt # (on the fly) 272 | = test 273 | 26 less $T/tests/test_rar # rar contents, needs unrar|rar|bsdtar 274 | ~ .* testok/a b 275 | 27 less tests/archive.tgz:test_rar # (on the fly), needs unrar|rar|bsdtar 276 | ~ .* testok/a b 277 | 28 less $T/tests/test_rar:testok/a\ b # extract file from rar, needs unrar|rar|bsdtar 278 | = test 279 | 29 less tests/archive.tgz:test_rar:testok/a\ b # (on the fly), needs unrar|rar|bsdtar 280 | = test 281 | 30 less $T/tests/test_cab # ms cabinet contents, needs cabextract 282 | ~ .* cabinet.txt 283 | 31 less tests/archive.tgz:test_cab # (on the fly), needs cabextract 284 | ~ .* cabinet.txt 285 | 32 less $T/tests/test_cab:a\ text.gz # extract gzipped file from cab, needs cabextract 286 | = test 287 | 33 less tests/archive.tgz:test_cab:a\ text.gz # (on the fly), needs cabextract 288 | = test 289 | 34 less $T/tests/test_7z # 7z contents, needs 7zz|7zr|7za 290 | ~ .* testok/aaa.txt 291 | 35 less tests/archive.tgz:test_7z # (on the fly), needs 7zz|7zr|7za 292 | ~ .* testok/aaa.txt 293 | 36 less $T/tests/test_7z:testok/a\|b.txt # extract file from 7z, needs 7zz|7zr|7za 294 | = test 295 | 37 less tests/archive.tgz:test_7z:testok/a\|b.txt # (on the fly), needs 7zz|7zr|7za 296 | = test 297 | 38 less $T/tests/test_iso # iso9660 contents, needs bsdtar|isoinfo 298 | ~ .* ISO.TXT|/ISO.TXT;1 299 | 39 less tests/archive.tgz:test_iso # (on the fly), needs bsdtar|isoinfo 300 | ~ .* ISO.TXT|/ISO.TXT;1 301 | 40 less $T/tests/test_iso:ISO.TXT # extract file from iso9660, needs bsdtar 302 | = test 303 | 41 less tests/archive.tgz:test_iso:ISO.TXT # (on the fly), needs bsdtar 304 | = test 305 | 42 less $T/tests/test_iso:/ISO.TXT\;1 # extract file from iso9660, needs isoinfo, not bsdtar 306 | = test 307 | 43 less tests/archive.tgz:test_iso:/ISO.TXT\;1 # (on the fly), needs isoinfo, not bsdtar 308 | = test 309 | 44 less $T/tests/test_ar # ar archive contents 310 | ~ .* a=b/? 311 | 45 less tests/archive.tgz:test_ar # (on the fly) 312 | ~ .* a=b/? 313 | 46 less $T/tests/test_ar:a=b # extract file from ar 314 | = test 315 | 47 less tests/archive.tgz:test_ar:a=b # (on the fly) 316 | = test 317 | 48 less $T/tests/test_cpio:textfile # extract from cpio needs cpio 318 | = test 319 | 49 less tests/archive.tgz:test_cpio:textfile # (on the fly) needs cpio 320 | = test 321 | ### uncompress tests not covered in archive tests 322 | 50 less tests/compress.tgz:test.tar.bz2:tests/textfile # extract from bzip2 323 | = test 324 | 51 less tests/compress.tgz:test.tar.lzip:tests/textfile # extract from lzip, needs lzip 325 | = test 326 | 52 less tests/compress.tgz:test.tar.lzma:tests/textfile # extract from lzma, needs lzma 327 | = test 328 | 53 less tests/compress.tgz:test.tar.xz:tests/textfile # extract from xz, needs xz 329 | = test 330 | ### call dd also for brotli to keep the script structure clean git #19 (revert) 331 | 54 less tests/compress.tgz:test.bro:tests/textfile # extract from brotli, needs brotli 332 | = test 333 | 55 less tests/compress.tgz:test.tar.zst:tests/textfile # extract from zstandard git #13,20,36,44, needs zstd 334 | = test 335 | 56 less tests/compress.tgz:test.tar.lz4:tests/textfile # extract from lz4 git #14, needs lz4 336 | = test 337 | ### filter tests, produce readable output 338 | 57 less tests/filter.tgz:test_utf16 # UTF-16 Unicode needs iconv 339 | = test 340 | 58 less tests/filter.tgz:test_latin1 # ISO-8859-1 encoded file needs iconv 341 | = äöü 342 | ### no output if file not modified (watch growing files) git #4,25 (revert) 343 | 59 less $T/tests/test_plain # plain text, no output from lesspipe.sh 344 | = test=a 345 | 60 less tests/filter.tgz:test_html # html text, needs html_converter 346 | ~ \s*test 347 | 61 less tests/filter.tgz:test_html:: # html unmodified text 348 | ~ 349 | 62 less tests/filter.tgz:test_pdf # pdf, needs pdftotext|pdftohtml,html_converter|pdfinfo 350 | = test 351 | 63 less tests/filter.tgz:test_ps # postscript, needs ps2ascii 352 | ~ .* test\r? 353 | 64 less tests/filter.tgz:test.class # java class file, needs procyon 354 | ~ public class test 355 | 65 less tests/filter.tgz:test_docx # docx (neu) git #24,26,27,37, needs pandoc|docx2txt|libreoffice 356 | = test 357 | 66 less tests/filter.tgz:test_pptx # pptx (neu), needs pptx2md,mdcat|pptx2md,pandoc|libreoffice,html_converter 358 | ~ processing slide 1...|.*test.* 359 | 67 less tests/filter.tgz:test_xlsx # xlsx (neu), needs in2csv|xlscat|excel2csv|libreoffice 360 | ~ ^test$ 361 | 68 less tests/filter.tgz:test_odt # odt, needs pandoc|odt2txt|libreoffice 362 | = test 363 | 69 less tests/filter.tgz:test_odp # odp, needs libreoffice,html_converter 364 | ~ \s*test 365 | 70 less tests/filter.tgz:test_ods # ods, needs xlscat|libreoffice,html_converter 366 | ~ test 367 | 71 less tests/filter.tgz:test_doc # doc (old), needs wvText|catdoc|libreoffice 368 | ~ *test 369 | 72 less tests/filter.tgz:test_ppt:ms-powerpoint # ppt (old), catppt not always working, needs libreoffice,html_converter 370 | ~ .*1. test|\s*test 371 | 73 less tests/filter.tgz:test_xls # xls (old), needs in2csv|xls2csv|libreoffice,html_converter 372 | ~ ^test$|^"test"$ 373 | 74 less tests/filter.tgz:test_ooffice1 # openoffice1 (very old), needs sxw2txt|libreoffice 374 | = test 375 | 75 less tests/filter.tgz:test_nroff # man pages etc (nroff), needs groff|mandoc 376 | ~ .* Commands 377 | 76 less tests/filter.tgz:test_rtf # rtf, needs unrtf|libreoffice 378 | ~ test 379 | 77 less tests/filter.tgz:test_dvi # dvi, needs dvi2tty 380 | ~ test 381 | 78 less tests/filter.tgz:test_so # shared library (.so) 382 | ~ .* T test 383 | 79 less tests/filter.tgz:test.pod # pod text, needs pod2text|perldoc 384 | ~ test 385 | 80 less tests/filter.tgz:test.pod: # unmodified pod text, needs pod2text|perldoc 386 | ~ test 387 | 81 less tests/filter.tgz:test_nc4 # netcdf, needs h5dump|ncdump 388 | ~ data:|\s*DATA . 389 | 82 less tests/filter.tgz:test_nc5 # hierarchical data format, needs h5dump|ncdump 390 | ~ data:|\s*DATA . 391 | 83 less tests/filter.tgz:test_matlab # matlab git #18, needs matdump 392 | ~ r 393 | 84 less tests/filter.tgz:matlab.mat # matlab, not recognized by file, needs matdump 394 | ~ r 395 | 85 less tests/filter.tgz:test_djvu # djvu, needs djvutxt 396 | = test 397 | 86 less tests/filter.tgz:test.pem # SSL related files git #15 398 | ~ .* 2038 GMT 399 | 87 less tests/filter.tgz:test.bplist # Apple binary property list, needs plistutil 400 | ~ 401 | ### no test case for decoding gpg/pgp encrypted files git #12 402 | 88 less tests/filter.tgz:test_mp3 # mp3 without mp3 extension, needs ffprobe|mediainfo|exiftool 403 | ~ *[Tt]itle *: *test 404 | 89 less tests/filter.tgz:test_mp3:mp3 # mp3, needs ffprobe|eyeD3|id3v2 405 | ~ *[Tt]itle *: *test 406 | 90 less tests/filter.tgz:test_data # binary data 407 | = test 408 | ### colorizing tests (ok should be displayed colored, for MacOSX see git #48) 409 | 91 less $T/tests # directory 410 | c test.jar 411 | 92 less tests/archive.tgz # contents of tar colorized with archive_color 412 | c test_cab 413 | 93 less $T/tests/test.c # C language (vimcolor) 414 | c void 415 | 94 LESSCOLORIZER=source-highlight less $T/tests/test.c # C language (source-highlight) git #3, needs source-highlight 416 | c void 417 | 95 less tests/filter.tgz:test.c # C language from file within archive 418 | c void 419 | 96 LESSCOLORIZER='pygmentize -O style=vim' less $T/tests/test.c # allow setting pygmentize style option git #5, needs pygmentize 420 | c void 421 | 97 cat $T/tests/test.c|less - :c # even colorize piped files 422 | c void 423 | 98 less tests/filter.tgz:test_html:html # html colorized text 424 | c head 425 | 99 less tests/filter.tgz:test.pod:pod # unmodified pod text, colorized, needs pod2text|perldoc 426 | c =head1 427 | 100 less tests/filter.tgz:test_plain:sh # plain text, force color (shellscript) 428 | c test 429 | 101 less tests/filter.tgz:index.rst # reStructuredText, needs mdcat 430 | c # test 431 | 102 less tests/filter.tgz:test.json # json, epub and ipynb also covered git #62 (fails if no syntax/json.vim), needs pandoc 432 | c false 433 | 103 LESSCOLORIZER=code2color less tests/filter.tgz:t.eclass # ebuild and eclass file git #9,38,39 434 | c test 435 | 104 less tests/filter.tgz:Makefile # bsd Makefile not (file 5.28) / is (5.39) correctly recognized git #10 436 | c PORTNAME 437 | 105 diff -u $T/tests/t.eclass $T/tests/test.c|less - :diff # unified diff piped through less works git #11 438 | c test=a 439 | ### github issues (solved and unsolved) and other test cases 440 | 106 LESSCOLORIZER=code2color less tests/special.tgz:a-r-R.pl # colorize works within archives 441 | c sub 442 | 107 LESSCOLORIZER=pygmentize less tests/filter.tgz:test_dtb # device tree blob, needs dtc 443 | c test 444 | 108 less $T/tests/a-r-R.pl # do not call vimcolor with -l extension git #77 445 | c sub 446 | 109 less $T/tests/special.tgz:.gitconfig # colorize known dotfiles git #154 447 | c name 448 | 110 LESS= less $T/tests/a-r-R.pl # name contains -r or -R git #78 449 | = sub test {} 450 | 111 less $T/tests/test_zip:non-existent-file # nonexisting file in a zip archive git #1 451 | ~ 452 | 112 LESS= less tests/dir.zip # do not colorize listing git #140 453 | ~ .* dir/ 454 | 113 less $T/tests/test\ \;\'\"\[\(\{ok # file name with chars such as ", ' ... 455 | = test 456 | 114 less tests/special.tgz:test\ \;\'\"\[\(\{ok # archive having a file with chars from [ ;"'] etc. in the name 457 | = test 458 | 115 less $T/tests/test\[a\]\(b\)\{c\}.zip # file name with parens, brackets, braces git #69 459 | ~ .*test\[a\]\(b\)\{c\} 460 | 116 less $T/tests/test\[a\]\(b\)\{c\}.zip:'test\[a\]\(b\)\{c\}' # contained file with parens etc. 461 | = test 462 | 117 less $T/tests/test\[a\]\(b\)\{c\}.zip # file name with parens, brackets, braces (on the fly) 463 | ~ .*test\[a\]\(b\)\{c\} 464 | 118 less $T/tests/test\[a\]\(b\)\{c\}.zip:'test\[a\]\(b\)\{c\}' # contained file with parens etc. (on the fly) 465 | = test 466 | 119 less $T/tests/special.tgz=aaa::b::c::d # file name with colon (use alternate separator) 467 | = test 468 | 120 less $T/tests/symlink # symbolic link to file name with special chars 469 | = test=a 470 | 121 cat $T/tests/test_zip|less # can use pipes with LESSOPEN =|-... git #2 471 | ~ .*10240.* 472 | 122 cat $T/tests/test_zip|less - :tests/test.tar # extract files from piped file 473 | ~ .* tests/textfile 474 | 123 cat $T/tests/test_zip|less - :tests/test.tar:tests/textfile # extract files from piped archive 475 | ~ test 476 | 124 cat $T/tests/test_plain|LESSCOLORIZER=code2color less # display piped text files 477 | ~ test=a 478 | 125 cat $T/tests/test_plain|less - :plain # display piped plain text files 479 | ~ test=a 480 | 126 less +F $T/tests/test_plain # watch growing files with +F git #4 481 | ~ test=a 482 | 127 less $T/tests/test_plain : # even watch growing files without +F 483 | ~ test=a 484 | 128 less $T/tests/test.jar # support for jar files git #8,22 485 | ~ .* META-INF/ 486 | ### colorize markdown files (mdcat) on MacOSX and iTerm2 (see git #48) 487 | -------------------------------------------------------------------------------- /tests/archive.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wofr06/lesspipe/b1a24aca6afe98d7c1aa3fae70f21aee44632c24/tests/archive.tgz -------------------------------------------------------------------------------- /tests/compress.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wofr06/lesspipe/b1a24aca6afe98d7c1aa3fae70f21aee44632c24/tests/compress.tgz -------------------------------------------------------------------------------- /tests/dir.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wofr06/lesspipe/b1a24aca6afe98d7c1aa3fae70f21aee44632c24/tests/dir.zip -------------------------------------------------------------------------------- /tests/filter.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wofr06/lesspipe/b1a24aca6afe98d7c1aa3fae70f21aee44632c24/tests/filter.tgz -------------------------------------------------------------------------------- /tests/special.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wofr06/lesspipe/b1a24aca6afe98d7c1aa3fae70f21aee44632c24/tests/special.tgz -------------------------------------------------------------------------------- /vimcolor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Syntax highlight text using Vim 4 | # simplified version to use it as a monolithic function within lesspipe 5 | # 6 | # This software is copyright (c) 2002-2006 by Geoff Richards. 7 | # 8 | # This software is copyright (c) 2011 by Randy Stauner. 9 | # 10 | # This software is copyright (c) 2021-2024 by Wolfgang Friebel. 11 | # 12 | # This is free software; you can redistribute it and/or modify it under 13 | # the same terms as the Perl 5 programming language system itself. 14 | # 15 | use warnings; 16 | use strict; 17 | use 5.9.3; 18 | use IO::File; 19 | use File::Copy; 20 | use File::Temp; 21 | use Term::ANSIColor qw(color colorvalid); 22 | use IPC::Open3; 23 | use Getopt::Std; 24 | 25 | our ($opt_c, $opt_h, $opt_l, $opt_L); 26 | &getopts('chl:L') || usage(); 27 | usage() if $opt_h; 28 | 29 | # do a clean up if we get a CTRL-C 30 | our ($tdir,$script_fh, $markup_fh); 31 | $SIG{INT} = sub { if ($tdir) { 32 | close $script_fh if $script_fh; 33 | close $markup_fh if $markup_fh; 34 | unlink $tdir; print "\n"; exit 1 }}; 35 | 36 | $tdir = File::Temp->newdir('/tmp/vimcolorXXXX'); 37 | my $defaults = set_defaults(); 38 | languages(@ARGV) if $opt_L; 39 | 40 | my ($file) = shift; 41 | my $filetype = $opt_l; 42 | my $report_colors = $opt_c; 43 | 44 | if (! $file or $file eq '-') { 45 | $file = "$tdir/inputfile"; 46 | File::Copy::syscopy(\*STDIN, $file); 47 | } 48 | 49 | my %ANSI_COLORS = ( 50 | Comment => 'blue', 51 | Constant => 'red', 52 | Identifier => 'cyan', 53 | Statement => 'yellow', 54 | PreProc => 'magenta', 55 | Type => 'green', 56 | Special => 'bright_magenta', 57 | Underlined => 'underline', 58 | Ignore => 'bright_white', 59 | Error => 'on_red', 60 | Todo => 'on_cyan', 61 | ); 62 | 63 | # get colors from the active color scheme 64 | my $cmds = "$tdir/commands"; 65 | my $colorfile = "$tdir/colors"; 66 | open F, ">$cmds" or die "$!"; 67 | print F "redir! >$colorfile|colorscheme|"; 68 | print F "hi $_|" for keys %ANSI_COLORS; 69 | print F "q!"; 70 | close F; 71 | run($defaults->{vim_command}, $defaults->{vim_options}, '-S', $cmds, $colorfile); 72 | open F, $colorfile or die $!; 73 | my ($key, %attrs); 74 | my %t = map {$_, $_} qw(bold underline reverse italic blink undercurl standout); 75 | $t{undercurl} = 'underline'; 76 | $t{standout} = 'reverse'; 77 | while () { 78 | last if /^unknown/; # skip rest of file, no color support 79 | next if /^\s*$/; 80 | $key = $1 if /^(\w+)\s+\w/; 81 | $attrs{$key} .= ($1 =~ /(\d+)/ ? "ANSI$1 " : "$1 ") if /ctermfg=(\w+)/; 82 | if (/term=([\w,]+)/) { 83 | for my $what (split /,/, $1) { 84 | $attrs{$key} .= "$t{$what} " if exists $t{$what}; 85 | } 86 | } 87 | } 88 | close F; 89 | my %COLORS = %ANSI_COLORS; 90 | for (keys %ANSI_COLORS) { 91 | $COLORS{$_} = $attrs{$_} if $attrs{$_} && $attrs{$_} !~ /\bnone\b/i; 92 | } 93 | 94 | # allow the environment to overwrite: 95 | 96 | if ($ENV{TEXT_VIMCOLOR_ANSI}) { 97 | my @ADD = split /\s*[=;]\s*/, $ENV{TEXT_VIMCOLOR_ANSI}; 98 | if (@ADD % 2) { 99 | warn "### TEXT_VIMCOLOR_ANSI has wrong content: $ENV{TEXT_VIMCOLOR_ANSI}\n"; 100 | } else { 101 | while (@ADD) { 102 | my ($k, $v) = splice @ADD, -2; 103 | $COLORS{$k} = $v; 104 | } 105 | } 106 | } 107 | 108 | # These extra syntax group are available but linked to the groups above by 109 | # default in vim. They can get their own highlighting (all_syntax_groups => 1). 110 | # Gets activated by setting a color for at least one of these members. 111 | my %SYNTAX_LINKS; 112 | 113 | $SYNTAX_LINKS{$_} = 'Constant' for qw(String Character Number Boolean Float); 114 | $SYNTAX_LINKS{$_} = 'Identifier' for qw(Function); 115 | $SYNTAX_LINKS{$_} = 'Statement' for qw(Conditional Repeat Label Operator Keyword Exception); 116 | $SYNTAX_LINKS{$_} = 'PreProc' for qw(Include Define Macro PreCondit); 117 | $SYNTAX_LINKS{$_} = 'Type' for qw(StorageClass Structure Typedef); 118 | $SYNTAX_LINKS{$_} = 'Special' for qw(Tag SpecialChar Delimiter SpecialComment Debug); 119 | 120 | my $all_groups = 0; 121 | $all_groups = 1 if grep {defined $COLORS{$_}} keys %SYNTAX_LINKS; 122 | # Copy ansi color for main group to all subgroups. 123 | $COLORS{$_} ||= $COLORS{$SYNTAX_LINKS{$_}} for keys %SYNTAX_LINKS; 124 | 125 | my %colors; 126 | my $reset = color('reset'); 127 | for (keys (%COLORS)) { 128 | if (colorvalid($COLORS{$_})) { 129 | $colors{$_} = color($COLORS{$_}); 130 | } else { 131 | warn "### invalid color name '$COLORS{$_}' (Term::ANSIColor) ###\n"; 132 | $colors{$_} = $reset; 133 | } 134 | } 135 | # Build a lookup table to determine if a syntax exists. 136 | my %SYNTAX_TYPE = map {$_, 1} keys %COLORS; 137 | 138 | my $syntax = do_markup($defaults, $file, $filetype); 139 | 140 | sub set_defaults { 141 | my $ex = 'vim'; 142 | $ex = 'nvim' if grep{ -e "$_/nvim" } split /:/, $ENV{PATH}; 143 | my @opts = $ex eq 'vim' ? qw(--not-a-term -XZ) : qw(headless); 144 | return { 145 | vim_command => $ex, 146 | vim_options => [@opts, qw(-R -i NONE -u NONE -N -n), "+set nomodeline"], 147 | all_syntax_groups => $all_groups, 148 | vim_let => {perl_include_pod => 1, 'b:is_bash' => 1}, 149 | }; 150 | } 151 | 152 | # Actually run Vim and turn the script's output into a datastructure. 153 | sub do_markup { 154 | my ($defaults, $file, $filetype) = @_; 155 | 156 | (my $filename = $file) =~s/"/\\"/g; 157 | 158 | # Create a temp file to put the output in. 159 | my $out_file = File::Temp->new(TEMPLATE => 'vimcXXXX', DIR => '/tmp'); 160 | # Create a temp file for the 'script', which is given to vim 161 | # with the -s option. 162 | my $script_file = "$tdir/scriptfile"; 163 | open $script_fh, ">$script_file" or die "$!\n"; 164 | my $markup_file = "$tdir/markupfile"; 165 | open $markup_fh, ">$markup_file" or die "$!\n"; 166 | my $filetype_set = defined $filetype ? ":set filetype=$filetype" : ''; 167 | my $vim_let = $defaults->{vim_let}; 168 | 169 | my $markscript= <<'EOF'; 170 | set report=1000000 171 | if !strlen(&filetype) 172 | filetype detect 173 | endif 174 | syn on 175 | new 176 | set modifiable 177 | set paste 178 | set isprint+=9 179 | wincmd p 180 | let s:end = line("$") 181 | let s:lnum = 1 182 | while s:lnum <= s:end 183 | let s:line = getline(s:lnum) 184 | let s:len = strlen(s:line) 185 | let s:new = "" 186 | let s:col = 1 187 | while s:col <= s:len 188 | let s:startcol = s:col " The start column for processing text 189 | let s:id = synID(s:lnum, s:col, 1) 190 | let s:col = s:col + 1 191 | while s:col <= s:len && s:id == synID(s:lnum, s:col, 1) | let s:col = s:col + 1 | endwhile 192 | let s:id = synIDtrans(s:id) 193 | let s:name = synIDattr(s:id, 'name') 194 | let s:new = s:new . '>' . s:name . '>' . substitute(substitute(substitute(strpart(s:line, s:startcol - 1, s:col - s:startcol), '&', '\&a', 'g'), '<', '\&l', 'g'), '>', '\&g', 'g') . '<' . s:name . '<' 195 | if s:col > s:len 196 | break 197 | endif 198 | endwhile 199 | exe "normal \pa" . strtrans(s:new) . "\n\e\p" 200 | let s:lnum = s:lnum + 1 201 | + 202 | endwhile 203 | wincmd p 204 | normal dd 205 | EOF 206 | if ($defaults->{all_syntax_groups}) { 207 | my %a= (map {$_, 1} ('Normal', keys %ANSI_COLORS, keys %SYNTAX_LINKS)); 208 | printf $markup_fh "hi %-16s ctermfg=7\n", $_ for keys %a; 209 | } 210 | print $markup_fh $markscript; 211 | close $markup_fh; 212 | my @script_lines = ( 213 | map { "$_\n" } 214 | # do :edit before :let or the buffer variables may get reset 215 | ":edit $filename", 216 | ( 217 | map { ":let $_=$vim_let->{$_}" } 218 | grep { defined $vim_let->{$_} } 219 | keys %$vim_let 220 | ), 221 | 222 | ':filetype on', 223 | $filetype_set, 224 | ":source $markup_file", 225 | ":write! $out_file", 226 | ':qall!', 227 | ); 228 | 229 | print $script_fh @script_lines; 230 | close $script_fh; 231 | 232 | run( 233 | $defaults->{vim_command}, 234 | @{$defaults->{vim_options}}, 235 | $filename, ('-s' => "$script_file") 236 | ); 237 | 238 | my $data = do { local $/; <$out_file> }; 239 | 240 | # Given an array ref ($syntax), we add a new syntax chunk to it, unescaping 241 | # the text & making sure that consecutive chunks of the same type are merged. 242 | my ($syntax, $content, %types); 243 | $data =~ s/\x0D\x0A?/\n/g; 244 | $data =~ s/<(.*?)<>\1>//g; # repeating blocks of the same type 245 | for (keys %colors) { 246 | $types{$_} = 1 if $data =~ s/>$_>(.*?)<$_(.*?)>(.*?)<\1(.*?)>(.*?)<\1'<',g=>'>',a=>'&'); 253 | $data =~s/&([lga])/$s{$1}/g; 254 | $data =~ s/\^\[/\e/g; 255 | # get start and end of file correct 256 | $data .= $reset if $data !~ /\n$/; 257 | print $data if $data; 258 | 259 | return if ! $report_colors; 260 | print "\n### Report color settings:\n"; 261 | print "$_ ($colors{$_}$COLORS{$_}$reset)\n" for keys %types; 262 | } 263 | 264 | # This is a subroutine which runs a program. 265 | # It takes a list of the program name and arguments. 266 | sub run { 267 | my ($prog, @args) = @_; 268 | 269 | { 270 | my ($in, $out) = (Symbol::gensym(), Symbol::gensym()); 271 | my $err_fh = Symbol::gensym(); 272 | 273 | my $pid = IPC::Open3::open3($in, $out, $err_fh, $prog => @args); 274 | 275 | # close these to avoid any ambiguity that might cause this to block 276 | # (see also the paragraph about "select" in IPC::Open3) 277 | close($in); 278 | close($out); 279 | 280 | # read handle before waitpid to avoid hanging on older systems 281 | my $errout = do { local $/; <$err_fh> }; 282 | 283 | my $gotpid = waitpid($pid, 0); 284 | die "couldn't run the program '$prog'" if $gotpid == -1; 285 | my $error = $? >> 8; 286 | if ($error) { 287 | $errout =~ s/\n+\z//; 288 | my $details = $errout eq '' ? '' : 289 | "\n$prog wrote this error output:\n$errout\n"; 290 | die "$prog returned an error code of '$error'$details"; 291 | } 292 | } 293 | } 294 | 295 | sub languages { 296 | my ($pat) = @_; 297 | $pat ||= ''; 298 | my $cmds = "$tdir/commands"; 299 | my $typefile = "$tdir/types"; 300 | open F, ">$cmds" or die "$!"; 301 | print F "redir! >$typefile|echo getcompletion(\'$pat\', 'filetype')|q!"; 302 | close F; 303 | run($defaults->{vim_command}, $defaults->{vim_options}, '-S', $cmds, $typefile); 304 | open F, $typefile or die $!; 305 | $_ = ; $_ = ; 306 | s/[\[\]',]//g; 307 | print "$_\n"; 308 | close F; 309 | exit; 310 | } 311 | 312 | sub usage { 313 | print <