So I have a few directories and I want to copy only the name of the directories that start with 'k' and end with 'g' to a file. How do I do that?
3 Answers
With zsh
:
print -rC1 -- k*g(N/) > dirnames.txt
Note however that since newline is as valid a character as any in a file name, that means you won't be able to distinguish two files called k1g
and k2g
from one file called k1g<newline>k2g
.
Instead of printing those dirnames one per line, you could print them NUL-delimited by adding the -N
option to print
.
Then, you can post-process the resulting file with things like dirs=( ${(0)"$(<dirnames.nul-delimited)"} )
or xargs -r0a dirnames.nul-delimited some-command
.
To also include the name (base name / t
ail) of the k*g
files found in subdirectories, you'd do:
print -rNC1 -- **/k*g(ND/:t) > dirnames.nul-delimited
Note that it could have duplicates if there's both a a/king
and .git/king
files for instance. You could remove duplicates with:
() { print -rNC1 -- ${(u)argv} > dirnames.nul-delimited; } **/k*g(ND/:t)
An approach to have a file list that is both human readable and post-processable would be to use shell quoting around the file names:
print -rC1 -- k*g(N/:q) > dirnames.txt
Here using a zsh
-specific variant of quoting. Or using a portable (in sh-like shells) and more reliable quoting syntax:
() { print -rC1 -- ${(qq)argv} > dirnames.txt; } k*g(N/)
Or using GNU ls
:
ls -Ud --quoting-style=shell-always -- k*g(N/) > dirnames.txt
That list can then later be retrieved with:
dirs=( ${(Q)${(zZ[n])"$(<dirnames.txt)"}} )
In zsh
.
Or in both zsh
or bash
(or ksh93 if you declared dirs
as array first):
eval "dirs=( $(<dirnames.txt) )"
If you don't have or can't use zsh
for some reason, but are on a GNU system, and your filenames are made on valid text in the locale, you could do something approaching with:
print one per line (assuming file names don't contain newline characters):
find . -mindepth 1 -maxdepth 1 -name 'k*g' -type d -printf '%f\n' | sort > dirnames.txt
same NUL-delimited:
find . -mindepth 1 -maxdepth 1 -name 'k*g' -type d -printf '%f\0' | sort -z > dirnames.txt
recursive, nul-delimited:
find . -name 'k*g' -type d -printf '%f\0' | sort -z > dirnames.txt
same deduplicated:
find . -name 'k*g' -type d -printf '%f\0' | LC_ALL=C sort -zu | sort -z > dirnames.txt
quoted (though sorted in batches)
find . -mindepth 1 -maxdepth 1 -name 'k*g' -type d -printf '%f\0' | xargs -r0 ls -d --quoting-style=shell-always --
quoted and properly sorted:
find . -mindepth 1 -maxdepth 1 -name 'k*g' -type d -printf '%f\0' | sort -z | LC_ALL=C gawk -v 'RS=\0' -v q="'" ' shquote(s) { gsub(q, q "\\" q q, s) return q s q } {print shquote($0)}' | xargs -r0 ls -d --quoting-style=shell-always --

- 544,893
Probably best not to use ls
, because of this.
Try:
find * -maxdepth 0 -type d -name 'k*g' > filename.txt
(Without -maxdepth 0
it'll also do subfolders of folders. Use -iname
instead of -name
if uppercase K and G should also count.)
If there's any possibility that you'll have folders or files that begin with hyphens (but who does that?) you can use this instead:
find . -maxdepth 1 -type d -name 'k*g' | sed 's/^\.\///' > filename.txt
Notice the switch from -maxdepth 0
to -maxdepth 1
.

- 8,691
-
1I suspect this isn't actually better than my answer, although I would encourage folks to use this answer instead because it isn't worse and it might be better. why wouldn't it be better? the core issue is that filenames can contain newlines, and both this answer and mine will dump those newlines into
filename.txt
in a very confusing way that breaks the "one-record-per-line" convention. you could use-print0
, but now whatever's consumingfilename.txt
has to expect NUL-delimited filenames... it's a nasty problem. – strugee Apr 04 '22 at 21:38 -
1That fails if there are filenames that start with
-
or arefind
predicates (with potentially severe consequences if one is called-delete
for instance). – Stéphane Chazelas Apr 05 '22 at 05:16 -
@StéphaneChazelas Although part of me feels that anyone who names a file
-delete
deserves whatever happens to them, I have edited the answer with an alternative suggestion for anyone worried about it. – frabjous Apr 05 '22 at 14:25 -
Another reason not to expand
*
is that it may expand to too many names, which would trigger an error when the shell tries to run thefind
command. Note that thesed
expression could be shortened tos/..//
, but that it would be better to do that fromfind
in a short script called from-exec
, to avoid issues with names containing newlines. – Kusalananda Apr 05 '22 at 17:38 -
@Kusalananda, here
sed 's/^\.\///'
is fine wrt newline as with-maxdepth 1
,/
can't occur anywhere else in the file path.sed 's/..//'
would be a problem though. One could shorten it tosed 's|./||'
orsed s:./::
if they felt so inclined. – Stéphane Chazelas Apr 05 '22 at 19:06 -
@StéphaneChazelas Ah, with
-maxdepth
it's safe. So it is. I think I would personally prefer doing it with a parameter substitution in a shell called from-exec
though (or with a-printf
format if it was GNUfind
), which would make it future-proof for when the code is reused for something else. – Kusalananda Apr 05 '22 at 23:05
You can use shell globbing along with redirection:
$ ls -d k*g/ > filename.txt
This will break if you have directories whose names contain newline characters. (I think it will only pick up directories and not files, but I'm not in front of a computer to check.)

- 14,951
-
It will only match directories, however
ls
by default will output their contents unless passed the-d
option I think? – steeldriver Mar 31 '22 at 17:35 -
-
It also includes symlinks to directories. Note that it's the shell doing it, not
ls
.ls
just prints its arguments here. – Stéphane Chazelas Apr 05 '22 at 05:14
printf '%s\n' k*g/ > dirlist.txt
? – Fravadona Apr 04 '22 at 22:47