5

I want to make a script to "unhide" all files and directories inside a certain directory in one go, e.g. ./unhide test.

test/
├── sub1
│   └── .hiddenfile1
└── sub2
    └── .hiddendir
        ├── .hiddendirsub
        ├── .hiddenfile2
        └── not.hidden

Desired outcome:

test/
├── sub1
│   └── hiddenfile1
└── sub2
    └── hiddendir
        ├── hiddendirsub
        ├── hiddenfile2
        └── not.hidden

How can I do that?

I'm still new to this and I've been trying to find a solution using find, but stuck at -exec, and rename (or mv) because I'm still struggling to understand how this combination works. :( So, I'll appreciate if anyone here can give a solution and detailed explanation as well. Thanks.

muru
  • 207,228

2 Answers2

7

You can do that with find as follows:

find /path/to/test -depth -name ".*" -execdir rename -n 's|/\.|/|' {} +

This only prints the renaming actions, if it shows what you want remove the -n option.

Explanations

  • -depth – lets find process from bottom to top, renaming files before their parent directories
  • -name ".*" – lets find search for files (everything is a file) beginning with a dot – the dot is literal here, as this is not a regular expression
  • -execdir … + – execute in the matched file‘s directory
  • rename 's|/\.|/|' {} – replace the first occurence of “/.” from the matched file’s path (find’s placeholder for which is {}) with “/”, essentially removing the dot from the beginning of the filename
    This would be e.g. rename 's|/\.|/|' ./.hiddenfile1 in your case, this would be renamed to ./hiddenfile1.

Example run

$ tree -a
.
├── sub1
│   └── .hiddenfile1
└── sub2
    └── .hiddendir
        ├── .hiddendirsub
        ├── .hiddenfile2
        └── not.hidden

$ find ~/test -depth -name ".*" -execdir rename 's|/\.|/|' {} +
$ tree -a
.
├── sub1
│   └── hiddenfile1
└── sub2
    └── hiddendir
        ├── hiddendirsub
        ├── hiddenfile2
        └── not.hidden

Usage in a script

In a script you can simply use positional parameters instead of the path, it may be relative or absolute – just remember to quote correctly:

#!/bin/bash
find "$1" -depth -name ".*" -execdir rename -n 's|/\.|/|' {} +
dessert
  • 40,956
3
$ tree -a test
test
├── .alsohidden
├── sub1
│   └── .hiddenfile1
└── sub2
    ├── .hiddendirsub
    ├── .hiddenfile2
    └── not.hidden

3 directories, 4 files

$ find test/ -depth -name ".*" -exec rename -n 's|(.*/)\.(.*)|$1$2|' {} +
rename(test/.alsohidden, test/alsohidden)
rename(test/sub2/.hiddendirsub, test/sub2/hiddendirsub)
rename(test/sub2/.hiddenfile2, test/sub2/hiddenfile2)
rename(test/sub1/.hiddenfile1, test/sub1/hiddenfile1)
  • find test/ begin paths with test/, search recursively from this path
  • -depth stolen from dessert's answer - seemed like a good idea
  • -name ".*" filename starts with .
  • -exec command {} + run command on the found files, constructing an argument list.
  • rename -n do not do anything, only show what will be done (remove -n after testing to actually rename)
  • s|old|new| replace old with new
  • (.*/)\.(.*) save all the chars up to and including the last directory separator/, skip a literal ., save all the subsequent characters
  • $1$2 print the saved patterns
Zanna
  • 72,312