Single line based solution, (to run in Terminal):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $'\0' f ; do { echo "${f}"; }; done; unset f;
for your/OP's case, change the "./"
into "/home/john/my directory/"
To use in a script file:
#!/bin/bash
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $'\0' f ; do {
echo "${f}";
# your other commands/codes, etc
};
done;
unset f;
Above functionality can be achieved in this (recommended) way too:
#!/bin/bash
while IFS= read -r -d $'\0' f ; do {
echo "${f}";
# your other commands/codes, etc
};
done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0);
unset f;
Brief/Short DESCRIPTION:
• "./"
: this is the current directory. specify a directory path.
• -not -type d
: here the -not
is configuring it to skip the next mentioned type, & next mentioned -type
is d
= directories, so it will skip directories. Use f
instead of d
to skip files. Use l
instead of d
to skip symlink-files.
• -maxdepth 1
: is configuring it to find files within current (aka: one) directory level only. To find file inside each & first sub-directory level, set maxdepth to 2. If -maxdepth not used, then it will search recursively (inside sub-directories), etc.
• -iname "*.jpg"
: here the -iname
is configuring it to find files and ignore (upper/lower)-case in filename/extension. The -name
does not ignore case. The -lname
finds symlinks. etc.
• -print0
: it prints pathname of current file to standard output, followed by an ASCII NUL character (character code 0), which we will later detect by using the read
in while
.
• IFS=
: here its used in case a filename ends with a space. We are using NUL / ''
/ \0
with IFS to detect each found filename. As "find" is configured to separate them with \0
which is produced by -print0
.
• read -r -d $'\0' fileName
: the $'\0'
is ''
/ NUL . The -r
was used in case a file name has a backslash.
• read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...] [...]
-r Backslash does not act as an escape character. The backslash is considered to be part of the line. In particular, a backslash-newline pair may not be used as a line continuation.
• done < <(...)
: Process-Substitution used here to send/pipe output of "find" into the "read" of "while"-loop. More info: https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html , https://wiki.bash-hackers.org/syntax/expansion/proc_subst , https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB , https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP
In other answer by @John1024, he has shown great bash-based solution, that does not use the "find", an external utility.
"find" is very effective & fast, i prefer it when there is too many files to handle.
in @John1024's solution it will print out the matching-rule line when there is no file in the directory, so below [ ! -e "${f}" ]...
line is used to skip that,
here is single-line solution to use in Terminal directly:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;
Here is a script:
#!/bin/bash
DIR="/home/john/my directory/";
for f in "$DIR"*.txt ; do {
[ ! -e "${f}" ] && continue;
echo "${f}";
# your codes/commands, etc
};
done;
unset DIR;
Note: if the directory in DIR has "/"
slash (directory indicator) at-end, then in matching-rule, again using the "/"
is not necessary,
Or, do opposite: in DIR do not use the "/"
at-end and so use it in matching-rule "$DIR"/*.txt
That extra checking by using the [ ! -e "${f}" ]...
code can be avoided, if below shell-option (aka: "shopt") is used or enabled:
shopt -s nullglob
If a shopt state was changed by a script, then in other bash based script program it creates unexpected/unanticipated problems.
To have consistent behavior across all scripts that uses bash, bash-shell-option's state should be recorded/saved inside your script, and once you're done using your primary functions in your script, then that shell-option should be set back to previous settings.
We use backtick (aka: grave-accent, aka: backquote) `...`
command substitution (for internal bash-command codes, etc), to not-spawn a new sub-shell, retain literal meaning of backslash, for wider support (aka: portability), etc, As backtick based internal bash-commands, etc can be often executed in same shell as the script, etc, And so it is little-bit faster & better, and also better for the purpose we are handling here. If you prefer $(...)
command-substitution then use that, anyone have freedom & right to choose what they prefer, avoid, etc. More info: here.
So above script is again shown, & this time with previous settings of a shopt restored, before ending the script:
#!/bin/bash
DIR="/home/john/my directory/";
ub="/usr/bin";
shopt = shell-option(s).
Setting-up "previous-nullglob" state to "enabled"/"on"/"1":
p_nullglob=1;
The "shopt -s" command output shows list of enabled shopt list, so if
nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0
[ "shopt -s | ${ub}/grep nullglob
" == "" ] && p_nullglob=0;
Enabling shell-options "nullglob":
shopt -s nullglob;
previous code, but without the extra checking [ ! -e "${f}" ]... line:
for f in "$DIR"*.txt ; do {
echo "${f}";
# your codes/commands, etc
};
done;
As we have utilized enabled nullglob shopt, its now in enabled state,
so if previously it was disabled only-then we will disable it:
[ "$p_nullglob" -eq "0" ] && shopt -u nullglob;
unset DIR;
unset p_nullglob ub;
Output of shopt -p shoptName
(for example: shopt -p dotglob
) can be
either, this: shopt -u shoptName
(the u
is unset
/disabled/off/0
)
or, this: shopt -s shoptName
(the s
is set
/enabled/on/1
)
The position of the letter "s"
or "u"
is always at 7
(because, in bash, a string's letter position begins from 0
, that is, the 1st letter of a string is at position 0
)
We can obtain this "u"
or "s"
and store it in a variable, so that we can use it to restore previous state.
And if we apply this (mentioned in above) way to save/restore shopt state, then we can avoid using external tool "grep"
.
To view "txt"
file that begins with "."
, that is, to view hidden "txt"
file, we need to enable "dotglob"
shopt.
So this time in below, "dotglob"
is included & enabled to show HIDDEN "txt"
files:
#!/bin/bash
DIR="/home/john/my directory/";
p_nullglob="u";
pSS="shopt -p nullglob
";
[ "${pSS:7:1}" == "s" ] && p_nullglob="s";
p_dotglob="u";
pSS="shopt -p dotglob
";
[ "${pSS:7:1}" == "s" ] && p_dotglob="s";
shopt -s nullglob dotglob;
for f in "$DIR"*.txt ; do {
echo "${f}";
# your codes/commands, etc
};
done;
[ "$p_nullglob" == "u" ] && shopt -u nullglob;
[ "$p_dotglob" == "u" ] && shopt -u dotglob;
unset DIR;
unset p_nullglob p_dotglob pSS;
There are more simple way to save/restore shopt option/value.
Isaac posted here, how to save+restore Env/Shopt variable/option state/value.
Saving shopt state of "nullglob":
p_nullglob="`shopt -p nullglob`";
... # your primary-function codes/commands, etc lines
Restoring back previous shopt state of "nullglob", before exiting script:
eval "$p_nullglob" ;
Multiple shopt states can be saved this way:
p_multipleShopt="`shopt -p nullglob dotglob`";
and restore process is same as before:
eval "$p_multipleShopt" ;
Save ALL shopt states this way:
p_allShopt="`shopt -p`";
and restore process is same as before:
eval "$p_allShopt" ;
So here is another bash based solution:
#!/bin/bash
DIR="/home/john/my directory/";
p_allShopt="shopt -p
";
shopt -s nullglob dotglob;
for f in "$DIR"*.txt ; do {
echo "${f}";
# your codes/commands, etc
};
done;
eval "$p_allShopt" ;
unset DIR p_allShopt;
Using eval
is safe in above, as the variable "$p_allShopt"
is not holding a data provided by a user or a data thats not-sanitized, That var is holding output of bash internal command shopt
.
If you still want to avoid eval
then use below solution:
#!/bin/bash
DIR="/home/john/my directory/";
p_allShopt="shopt -p
";
shopt -s nullglob dotglob;
for f in "$DIR"*.txt ; do {
echo "${f}";
# your codes/commands, etc
};
done;
while IFS= read -a oneLine ; do {
${oneLine} ;
};
done < <(echo "$p_allShopt") ;
unset DIR p_allShopt oneLine;
Few (other) notable & related SHOPT that may be useful, are:
- nocaseglob : If set, Bash matches filenames in a case-insensitive fashion when performing filename expansion.
- nocasematch : If set, Bash matches patterns in a case-insensitive fashion when performing matching while executing
case
or [[
conditional commands, when performing pattern substitution word expansions, or when filtering possible completions as part of programmable completion.
- dotglob : If set, Bash includes filenames beginning with a
‘.’
in the results of filename expansion. The filenames ‘.’
and ‘..’
must always be matched explicitly, even if dotglob is set.
- nullglob : If set, Bash allows filename patterns which match no files to expand to a null string, rather than themselves.
- extglob : If set, the extended pattern matching features described above (see Pattern Matching) are enabled.
- globstar : If set, the pattern
‘**’
used in a filename expansion context will match all files and zero or more directories and subdirectories. If the pattern is followed by a ‘/’
, only directories and subdirectories match.
– pospi Aug 06 '16 at 07:28for f in "$DIR"/*.txt
= finefor f in "$DIR/*.txt"
= breaks