Shell Tip Of the Day - Selecting untracked files

Posted on Fri 01 June 2018 in Posts

Today's tip of the day is a follow up from something I learned at this year's Polyglot UnConference -- how to use select with the Bash shell for interactive goodness.

At Polyglot I saw an example that looked like this:

select x in * ; do stat $x; done

Try this (go to a terminal and in the bash shell type it verbatim). Go ahead, I'll wait.

Did that? Cool, so as you saw the select keyword is this way of interactively selecting items from a glob with bash. In that above example, it allowed you to stat a file you select.

Today I found a practical application of this: interactively deleting untracked files from a checked out Git repo.

My scenario: I was working on a future blog post and had created a handful of image files in the current directory. This isn't uncommon: I'll often get a single image that I want to use for something like the OpenGraph summary image for a post, and then I edit it via ImageMagick to resize it, change a background to transparent, etc, and then I run it through Crunch to shrink it down.

Today I found that after getting the final image I wanted, a git status showed a number of untracked files I didn't care about:

$ git status
On branch codependentcodr/ch202/vscode-python-debugging-unit-tests-tasks
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        content/python-and-vs-code-part1.md
        content/static/imgs/vscode141.png
        content/static/imgs/vscodeAndPython.png
        content/static/imgs/vscodelogo-crunch.png
        content/static/imgs/vscodelogo.png

nothing added to commit but untracked files present (use "git add" to track)

The only file I wanted from content/static/imgs was vscodeAndPython.png, all the other stuff in content/static/imgs was junk that could be deleted. I could manually delete each one, typing the full filename each time, or I could save some keystrokes by using select and a little command-line fu:

select x in git status --porcelain | tr -d \?\?; do rm $x ; done

Explanation: git status --porcelain just spits out the filenames from a git status (no header/footer, extra stuff), and tr -d \?\? removes the leading ?? that's prepended to any filename that is untracked. The select iterates over each file matched by this command in an interactive way, and then the do rm $x removes whatever file I select. This allowed me to interactively delete files that were untracked:

$ select x in `git status --porcelain | tr -d \?\?`; do rm $x ; done
1) content/python-and-vs-code-part1.md        4) content/static/imgs/vscodelogo-crunch.png
2) content/static/imgs/vscode141.png          5) content/static/imgs/vscodelogo.png
3) content/static/imgs/vscodeAndPython.png
#? 2
#? 4
#? 5
$ git status
On branch codependentcodr/ch202/vscode-python-debugging-unit-tests-tasks
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        content/python-and-vs-code-part1.md
        content/static/imgs/vscodeAndPython.png

nothing added to commit but untracked files present (use "git add" to track)

I thought this was pretty cool, next time you find yourself wanting to choose from a number of files on the command line, think about select. 😄

Ok: I realize I could've just added the md file & the image I wanted to git and then deleted all untracked files, but would that have been as interesting as this blog post? I think not.