5

I'm trying to align output from a bash for loop.

Currently, I'm getting output from my loop that looks like so:

Directory:  /some/long/directory/path  Remote:  some-remote
Directory:  /some/dir/path  Remote:  other-remote

Which I'm trying to align like so:

Directory:  /some/long/directory/path  Remote:  some-remote
Directory:  /some/dir/path             Remote:  other-remote

The current, basic loop that generates this output looks something like this:

for dir in $(find /some/path -type d -name .git); do
    cd $dir
    remote=$(git remote)
    printf "Directory: $dir\tRemote: $remote\n
done

I've tried using:

  • column (which formats each line separately, as it's a for loop)
  • printf (printf "Directory: %s Remote: %s\n" "$dir" "$remote")
  • awk (echo "Directory: $dir Remote: $remote" | awk '{printf ("%s-20s %s-20s %s-20s %s-20s",$1 $2 $3 $4)}')

Among many other variations of these commands.

I'm probably missing something basic (I tried my best at looking at other examples online and reading the man pages), but I couldn't get it to work.

I'd really appreciate any pointers as to what I'm doing wrong.

0

3 Answers 3

6

column should work just fine. However, you don't add it to each loop iteration, but in the end:

for
...
done | column -t

Output:

Directory:  /some/long/directory/path  Remote:  some-remote
Directory:  /some/dir/path             Remote:  other-remote

Some additional notes regarding your script:

  • Do not loop find output like this. Check here.
  • Quote file/directory variables --> cd "$dir"
  • Do not use Variables in printf FORMAT string. --> printf 'Directory: %s\tRemote: %s\n' "$dir" "$remote"
5

You can use column, but

Since you indicate that your shell is bash, you can instead use the globstar and nullglob (and, as noted by @Kusalananda, the dotglob) options to iterate directly from the shell:

shopt -s globstar
shopt -s nullglob
shopt -s dotglob
for d in **/.git/
do
  cd "$d"
  printf "Directory: %s\tRemote: %s\n" "${d%/.git/}" "$(git remote)"
  cd - >/dev/null
done | column -t -s $'\t'
  • The globstar option enables the ** glob (aka "wildcard") that matches any depth of intermediate directories, thereby allowing you to descend into the directory tree within a loop.
  • The nullglob option will ensure that you don't iterate at all if there is no matching directory. Without it, the glob pattern will be taken as refering to a directory with literal name **/.git/ in case no filename matches, and the loop will be executed once with this (non-sensical) value for $d.
  • The dotglob option will ensure that the ** also matches "hidden" intermediate directories (i.e. those that begin with a ., as in some/directory/.path/repository).
  • Since you don't need the .git/-part of the path, the shell string processing directive ${d%/.git/} will remove the last appearence of /.git/ from the value of $d.
  • The output within the loop is TAB-separated, so the column command is instructed via -s $'\t' to take a TAB character as input column separator rather than the default (which is space-separated columns).
1
  • I wasn't aware of this - thank you for letting me know. If I manage to format a loop without find, I'll be sure to post it here.
    – PGEL
    Commented Jul 27, 2021 at 7:47
4

Rewriting your loop:

find /some/path -type d -name .git -exec sh -c '
    for dirpath do
        printf "Directory: %s@Remote: %s\n" \
            "${dirpath%/.git}" \
            "$( git -C "$dirpath" remote )"
    done' sh {} + |
column -s '@' -t

This uses column to format the output of find. The find command finds all .git directories and outputs the needed info with the help of an in-line sh -c script that is called with batches of found directory paths. For output, the in-line script modifies the given directory paths to not include the actual /.git at the end, which means they would instead point to the Git project directory.

The output is tabulated with @ characters, which column later uses to aligned the data. Change the @ in the in-line sh -c script and in the call to column if you need to use another character.

Related:


If you have Git repositories with multiple remotes, you may want to duplicate the output for each remote separately:

find /some/path -type d -name .git -exec sh -c '
    for dirpath do
        git -C "$dirpath" remote |
        while IFS= read -r remote; do
            printf "Directory: %s@Remote: %s\n" \
                "${dirpath%/.git}" "$remote"
        done
    done' sh {} + |
column -s '@' -t

Here, we simply read lines from git -C "$dirpath" remote. Each line will contain the name of a Git remote, and for each read remote we do our output.

This may output

Directory: /some/path/src  Remote: origin
Directory: /some/path/src  Remote: private

if the repository at /some/path/src has two remotes.


Just for fun, we want JSON output:

find /some/path -type d -name .git -exec sh -c '
    for dirpath do
        git -C "$dirpath" remote |
        while IFS= read -r remote; do
            jo directory="${dirpath%/.git}" remote="$remote"
        done
    done' sh {} +

Possible output:

{"directory":"/some/path/src","remote":"origin"}
{"directory":"/some/path/src","remote":"private"}
{"directory":"/some/path/yash-shell","remote":"origin"}
{"directory":"/some/path/zsh-shell","remote":"origin"}
{"directory":"/some/path/datamash","remote":"origin"}

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.