4

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
  • 197,895
  • 55
  • 485
  • 740
  • You are aware that changing the filename (removing the leading dot) might break functionality that expect the hidden filename? – Soren A Nov 02 '18 at 14:40
  • @SorenA Yes. Fortunately, I'm not going to change/mess with those kinds of files (files that "need" to be hidden). – Lukman Hakim Nov 02 '18 at 15:00

2 Answers2

6

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
  • 39,982
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
  • 70,465
  • 1
    Why does rename accept “|” as a delimiter, but not “_”? Should I ask a new question? ;) – dessert Nov 02 '18 at 13:53
  • 1
    @dessert omg I just assumed it would allow, I always use | if I need / in the regex (unless I need |). That would be a good question. Maybe it has to be a non-word char or there's a set of permitted chars – Zanna Nov 02 '18 at 14:00
  • 1
    @Zanna I think this needs -depth like the answer from @dessert. I tried it and it gave errors Can't rename test/sub2/.hiddendir/.hiddenfile2 test/sub2/.hiddendir/hiddenfile2: No such file or directory Can't rename test/sub2/.hiddendir/.hiddendirsub test/sub2/.hiddendir/hiddendirsub: No such file or directory – Lukman Hakim Nov 02 '18 at 15:05
  • @LukmanHakim thanks for your advice - I added it, although I couldn't get those errors I'm probably just lucky :) – Zanna Nov 02 '18 at 18:06
  • @Zanna try with a path where both a directory and a file inside it are to be renamed, e.g. test/.dir/.file – without-depth,test/.diris renamed first and renamingtest/.dir/.file` then obviously fails. – dessert Nov 04 '18 at 19:01