5

I would like to print out in terminal tree like one below:

$ tree -a
.
└── .git
    ├── branches
    ├── config
    ├── description
    ├── HEAD
    ├── hooks
    │   ├── applypatch-msg.sample
    │   ├── commit-msg.sample
    │   ├── fsmonitor-watchman.sample
    │   ├── post-update.sample
    │   ├── pre-applypatch.sample
    │   ├── pre-commit.sample
    │   ├── prepare-commit-msg.sample
    │   ├── pre-push.sample
    │   ├── pre-rebase.sample
    │   ├── pre-receive.sample
    │   └── update.sample
    ├── info
    │   └── exclude
    ├── objects
    │   ├── info
    │   └── pack
    └── refs
        ├── heads
        └── tags

With graphically presented content of all files ie it should like respectively?

.
└── .git
    ├── branches
    ├── config
    |
    |   [core]
    |        repositoryformatversion = 0
    |        filemode = true
    |        bare = false
    |        logallrefupdates = true
    |
    ├── description
    |
    |   Unnamed repository; edit this file 'description' to name the repository.
    |
    ├── HEAD
    |
    |   ref: refs/heads/master
    |

Is there an easy way to reach that?

0

2 Answers 2

4

I'm not aware of an easy way to do that, but I wrote a script that does something similar. Instead of a fancy tree listing like tree does, I made it flat, like find.

Output (in an empty git repo like your example):

.git/
.git/branches/
.git/config
==> start .git/config <==
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
==> end .git/config <==

.git/description
==> start .git/description <==
Unnamed repository; edit this file 'description' to name the repository.
==> end .git/description <==

.git/HEAD
==> start .git/HEAD <==
ref: refs/heads/master
==> end .git/HEAD <==

.git/hooks/

...

(The ==> ... <== header/footer is inspired by tail)

Here's the script:

#!/bin/bash

# Globs include hidden files, are null if no matches, recursive with **
shopt -s dotglob nullglob globstar

for file in **; do
    # Print filename with an indicator suffix for filetype
    ls --directory --classify -- "$file"
    filetype="$(file --brief --mime-type -- "$file")"
    # Only print text files
    if [[ $filetype == text/* ]]; then
        printf '==> %s %s <==\n' start "$file"
        cat --show-nonprinting -- "$file"
        printf '==> %s %s <==\n' end "$file"
        echo
    fi
done

It's not pretty, but it works. Color makes it pretty at least:

#!/bin/bash

shopt -s dotglob nullglob globstar

for file in **; do
    ls --directory --classify --color=yes -- "$file"
    filetype="$(file --brief --mime-type -- "$file")"
    # Only print text files
    if [[ $filetype == text/* ]]; then
        printf '\e[32m==> %s %s <==\e[m\n' start "$file"
        cat --show-nonprinting -- "$file"
        printf '\e[31m==> %s %s <==\e[m\n' end "$file"
        echo
    fi
done

Screenshot:

Screenshot showing filename colorized by <code>ls</code>, "start" marker in green, and "end" marker in red

1
  • It's really cool, thank you for your hard work! 💪 Commented Nov 26, 2018 at 6:13
1

Like wjandrea's solution, this solution also flattens the tree to draw the output.

First install bat, the "cat clone with wings".1

sudo apt install bat

Now the solution is a one-liner using the the find command. find is preinstalled as part of GNU findutils.

find -type f -exec batcat {} +

The find -type f part lists all the files (and not the intermediate directories) in the current working directory. The -exec batcat {} + part passes the list of files to bat so that all may be printed with headers.

It acts as though the command were batcat .git/branches .git/config .git/description .git/HEAD .git/hooks/applypatch-msg.sample ....

The output looks like this:

───────┬────────────────────────────────────────────────────────────────────────
       │ File: .git/config
───────┼────────────────────────────────────────────────────────────────────────
   1   │ [core]
   2   │     repositoryformatversion = 0
   3   │     filemode = true
   4   │     bare = false
   5   │     logallrefupdates = true
   6   │ [remote "origin"]
   7   │     url = https://github.com/blah/blah.git
   8   │     fetch = +refs/heads/*:refs/remotes/origin/*
   9   │ [branch "master"]
  10   │     remote = origin
  11   │     merge = refs/heads/master
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
       │ File: .git/info/exclude
───────┼────────────────────────────────────────────────────────────────────────
   1   │ # git ls-files --others --exclude-from=.git/info/exclude
   2   │ # Lines that start with '#' are comments.
   3   │ # For a project mostly in C, the following would be a good set of
   4   │ # exclude patterns (uncomment them if you want to use them):
   5   │ # *.[oa]
   6   │ # *~
───────┴────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────
       │ File: .git/packed-refs
───────┼────────────────────────────────────────────────────────────────────────
   1   │ # pack-refs with: peeled fully-peeled sorted 
───────┴────────────────────────────────────────────────────────────────────────

In the teminal bat also colorizes the output and highlights syntax.

Screenshot of colored terminal output

bat shows a binary file like this to avoid printing junk to the screen.

───────┬────────────────────────────────────────────────────────────────────────
       │ File: .git/objects/06/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa   <BINARY>
───────┴────────────────────────────────────────────────────────────────────────

The binary detection isn't perfect so sometimes screen junk still appears.

───────┬────────────────────────────────────────────────────────────────────────
       │ File: .git/objects/84/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
───────┼────────────────────────────────────────────────────────────────────────
   1   │ x^A+)Q130040a50UMJ(I-.�-(�,K,I�-I�-�^A2�*^Sss^X��:r^V�o��yAP&�}ّ��͇^A�/
───────┴────────────────────────────────────────────────────────────────────────

You can make bat's output more or less fancy by adding options --style and --color. For details check the man page or the README on GitHub.


1: On Ubuntu 20 the package is called bat but the command is called batcat to resolve a conflict with another command called bat.

5
  • You should use find -type f -print0 | xargs -0 tail -n+0 to safely handle filenames, or simply find -type f -exec tail -n+0 {} +, but both of these have an edge case where tail could be ran with just one filename (depending on the total length of filenames), and so won't print the header
    – muru
    Commented Aug 4, 2023 at 12:33
  • @muru Does using -exec safely handle file names? If so you've just improved my solution by making it more robust and easier to type :-D I don't mind the one-file case because I probably will just cat the file directly in that case. Commented Aug 4, 2023 at 13:43
  • In this case, it will. The edge case isn't that of just one file, but when you have n filenames and find had to run tail with n-1 filenames (because of argument size limits) and then again with just 1 filename. Then that last file's contents will immediately follow the penultimate file's contents, without any header in between.
    – muru
    Commented Aug 4, 2023 at 13:58
  • I see your point about "argument size limits". For my use cases I don't expect to get anywhere near the limit. For me it's just a convenient way to show the file content of an small directory. Commented Aug 4, 2023 at 15:03
  • @muru, I completely rewrote my answer after discovering that the bat command is perfectly suited for my use case. I've incorporated your -exec improvement. bat prints a header with a single file argument. Your comments helped me learn a lot today! Commented Aug 4, 2023 at 16:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .