I have 5 empty directories:

[X]$ ls
dir_1  dir_2  dir_3  dir_4  dir_5

How can I make a text file in every one of these directories so that the text file would contain the name of the directory it is in? So for example, dir_1 would contain text file file_1.txt and this text file has text dir_1 in it, dir_2 contains text file file_2.txt that has text dir_2 in it and so on.

I searched for information but I couldn't figure this out. I started to learn linux very recently. Do I have to do something like:

find . -type d -exec touch (something?)

And is it possible to do this with for loops?

2 Answers 2

for d in dir_*; do
    printf "%s\n" "$d" > "$d/file_${d##*_}.txt"
  • $d expands to each directory name.
  • ${d##*_} expands to the trailing number in the directory name, because it removes everything up to the last underscore from $d. Read more in Parameter Expansion section in BASH manual or POSIX specification.
  • printf "%s\n" "$d" > "$d/file_${d##*_}.txt" writes the directory name to a txt file in that directory.

With an one-liner:

for d in dir_*; do printf "%s\n" "$d" > "$d/file_${d##*_}.txt"; done

I have favored printf to echo because there are special cases that are handled correctly by the former but not by the latter, although in the particular case presented echo would not arise problems. Find more in Why is printf better than echo?.

If you need to avoid non-directories that also begin with dir_, option 1 is to add the [ -d "$d" ] && test right before printf statement in the script above. Option 2 is to use dir_*/ so that only directories are matched:

for d in dir_*/; do
    printf "%s\n" "$dirn" > "${d}file_${dirn##*_}.txt"
  • 2
    You would need to use dir_*/ as the pattern to avoid non-directories, such as dir_listfile.
    – Kusalananda
    May 22, 2020 at 19:29
  • @Kusalananda Thank you for pointing that, I've updated the answer.
    – Quasímodo
    May 23, 2020 at 1:16
find . -name 'dir_[0-9]*'  -type d -execdir bash -c 'name=$0;printf "${name##*/}\n" > "$name/file_${name##*_}.txt"' '{}' \;

Execute multiple commands from find result using -execdir

name=$0; => store each directory name in $name variable and process it.

  • Sorry for being a pest here, but: 1) * isn't the only special character in dir_[0-9]\*, the full pattern should be quoted ('dir_[0-9]*'): as it is, it breaks if the current directory contains a directory named e.g. dir_1* (with a literal *). 2) Embedding {} in shell code is an injection vulnerability - it'd be better to use bash -c 'name="$1"; ...' sh '{}' \; (cont...)
    – fra-san
    May 22, 2020 at 15:20
  • (...cont) 3) please refer to my comment to the other answer about why printf should be preferred to echo. 4) Expansions should always be quoted, unless you really need them not to be: think about people who modifies your script to make it also find files whose name may contain spaces.
    – fra-san
    May 22, 2020 at 15:21
  • no problem , explanation about small things helps a lot ...your points are correct , also i would add that instead of bash -c 'name="$1"; ...' sh '{}' \; we can use bash -c 'name="$1"; ...' '{}' \; just without sh ....will edit my answer... May 22, 2020 at 15:27
  • The first argument after bash -c 'name="$1"; ...' is used as the name for the script and is assigned to $0, you need some string there, before '{}'. Otherwise $1 just remains unset (as you have seen). I'd also use "$name/file_${name##*_}.txt" (in double quotes). And give '%s\n' as a format string to printf.
    – fra-san
    May 22, 2020 at 15:36
  • yes , positional arguments starts with $0 , so i preferred that...thanks... May 22, 2020 at 15:45

You must log in to answer this question.

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