looping through `ls` results in bash shell script
Does any one have a template shell script for doing something with ls
for a list of directory names and looping through each one and doing something?
I'm planning to do ls -1d */
to get the list of directory names.
bash shell shell-script
add a comment |
Does any one have a template shell script for doing something with ls
for a list of directory names and looping through each one and doing something?
I'm planning to do ls -1d */
to get the list of directory names.
bash shell shell-script
add a comment |
Does any one have a template shell script for doing something with ls
for a list of directory names and looping through each one and doing something?
I'm planning to do ls -1d */
to get the list of directory names.
bash shell shell-script
Does any one have a template shell script for doing something with ls
for a list of directory names and looping through each one and doing something?
I'm planning to do ls -1d */
to get the list of directory names.
bash shell shell-script
bash shell shell-script
edited Sep 6 '17 at 13:30
Daniel A. White
asked Aug 28 '09 at 17:03
Daniel A. WhiteDaniel A. White
2,67311929
2,67311929
add a comment |
add a comment |
7 Answers
7
active
oldest
votes
Edited not to use ls where a glob would do, as @shawn-j-goff and others suggested.
Just use a for..do..done
loop:
for f in *; do
echo "File -> $f"
done
You can replace the *
with *.txt
or any other glob that returns a list (of files, directories, or anything for that matter), a command that generates a list, e.g., $(cat filelist.txt)
, or actually replace it with a list.
Within the do
loop, you just refer to the loop variable with the dollar sign prefix (so $f
in the above example). You can echo
it or do anything else to it you want.
For example, to rename all the .xml
files in the current directory to .txt
:
for x in *.xml; do
t=$(echo $x | sed 's/.xml$/.txt/');
mv $x $t && echo "moved $x -> $t"
done
Or even better, if you are using Bash you can use Bash parameter expansions rather than spawning a subshell:
for x in *.xml; do
t=${x%.xml}.txt
mv $x $t && echo "moved $x -> $t"
done
19
What if the filename has a space in it?
– Daniel A. White
Aug 28 '09 at 17:17
4
Unfortunately, as Daniel said, the code in this answer will break if any of the files or folders contains a space or newline in their name. It shows a very common misuse offor
loops, and a typical pitfall of trying to parse the output ofls
. @DanielA.White, you might consider unaccepting this answer if it wasn't helpful (or is potentially misleading), since like you said, you're acting on directories. Shawn J. Goff's answer should provide a more robust and working solution to your issue.
– slhck
Jan 23 '13 at 17:48
4
-∞ You shouldn't parsels
output, you shouldn't read output in afor
loop, you should use$()
instead of `` and Use More Quotes™. I'm sure @CoverosGene meant well, but this is just terrible.
– l0b0
Apr 17 '14 at 19:31
1
A better alternative if you really want to usels
isls -1 | while read line; do stuff; done
. At least that one won't break for whitespaces.
– Emmanuel Joubaud
Aug 1 '14 at 8:47
1
withmv -v
you don't needecho "moved $x -> $t"
– DmitrySandalov
Jan 15 '16 at 12:59
|
show 2 more comments
Using the output of ls
to get filenames is a bad idea. It can lead to malfunctioning and even dangerous scripts. This is because a filename can contain any character except /
and the null
character, and ls
does not use either of those characters as delimiters, so if a filename has a space or a newline, you will get unexpected results.
There are two very good ways of iterating over files. Here, I've used simply echo
to demonstrate doing something with the filename; you can use anything, though.
The first is to use the shell's native globbing features.
for dir in */; do
echo "$dir"
done
The shell expands */
into separate arguments that the for
loop reads; even if there is a space, newline, or any other strange character in the filename, for
will see each complete name as an atomic unit; it's not parsing the list in any way.
If you want to go recursively into subdirectories, then this won't do unless your shell has some extended globbing features (such as bash
's globstar
. If your shell doesn't have these features, or if you want to ensure that your script will work on a variety of systems, then the next option is to use find
.
find . -type d -exec echo '{}' ;
Here, the find
command will call echo
and pass it an argument of the filename. It does this once for each file it finds. As with the previous example, there is no parsing of a list of filenames; instead, a fileneame is sent completely as an argument.
The syntax of the -exec
argument looks a little funny. find
takes the first argument after -exec
and treats that as the program to run, and every subsequent argument, it takes as an argument to pass to that program. There are two special arguments that -exec
needs to see. The first one is {}
; this argument gets replaced with a filename that the previous parts of find
generates. The second one is ;
, which lets find
know this is the end of the list of arguments to pass to the program; find
needs this because you can continue with more arguments that are intended for find
and not intended for the exec'd program. The reason for the is that the shell also treats
;
specially - it represents the end of a command, so we need to escape it so that the shell gives it as an argument to find
rather than consuming it for itself; another way of getting the shell to not treat it specially is to put it in in quotes: ';'
works just as well as ;
for this purpose.
2
+1 this is definitely the way to go when you need to generate a file list and use it in a command. find -exec is limited by only being able to run a single commands. With the loop, you can pipe to your heart's content.
– MaQleod
May 10 '12 at 22:45
+1. I wish more people would usefind
. There is magic that can be done, and even the perceived limitations of-exec
can be worked around. The-print0
option is also valuable for use withxargs
.
– ghoti
Oct 17 '14 at 22:24
add a comment |
For files with spaces in you will have to make sure to quote the variable like:
for i in $(ls); do echo "$i"; done;
or, you can change the input field separator (IFS) environment variable:
IFS=$'n';for file in $(ls); do echo $i; done
Finally, depending on what you're doing, you may not even need the ls:
for i in *; do echo "$i"; done;
nice use of a subshell in the first example
– Jeremy L
Aug 28 '09 at 18:46
Why does IFS need a $ after the assignment and before the new character?
– Andy Ibanez
Nov 29 '12 at 18:57
3
Filenames can also contain newlines. Breaking onn
is insufficient. It's never a good idea to recommend parsing the output ofls
.
– ghoti
Oct 17 '14 at 22:18
add a comment |
If you have GNU Parallel http://www.gnu.org/software/parallel/ installed you can do this:
ls | parallel echo {} is in this dir
To rename all .txt to .xml:
ls *.txt | parallel mv {} {.}.xml
Watch the intro video for GNU Parallel to learn more:
http://www.youtube.com/watch?v=OpaiGYxkSuQ
add a comment |
Just to add to CoverosGene's answer, here is a way to list just the directory names:
for f in */; do
echo "Directory -> $f"
done
add a comment |
Why not set IFS to a newline, then capture the output of ls
in an array? Setting IFS to newline should resolve issues with funny characters in file names; using ls
can be nice because it has a built-in sort facility.
(In testing I was having trouble setting IFS to n
but setting it to newline backspace works, as suggested elsewhere here):
E.g. (assuming desired ls
search pattern passed in $1
):
SAVEIFS=$IFS
IFS=$(echo -en "nb")
FILES=($(/bin/ls "$1"))
for AFILE in ${FILES[@]}
do
... do something with a file ...
done
IFS=$SAVEIFS
This is especially handy on OS X, e.g., to capture a list of files sorted by creation date (oldest to newest), the ls
command is ls -t -U -r
.
2
Filenames can also contain newlines, and they often do when users are permitted to name their own files. Breaking onn
is insufficient. The only valid solutions use a for loop with pathname expansion, orfind
.
– ghoti
Oct 17 '14 at 22:21
The only reliable way to transfer a list of file names is to separate them with a NUL character, as this is the only one definitely not contained in a file path.
– glglgl
Jan 2 '15 at 9:23
add a comment |
This is how I do it, but there are probably more efficient ways.
ls > filelist.txt
while read filename; do echo filename: "$filename"; done < filelist.txt
6
Stick to pipes in place of the file: >ls | while read i; do echo filename: $i; done
– Jeremy L
Aug 28 '09 at 18:46
Cool. I should say that you can also use $EDITOR filelist.txt in between the two commands. Lots of stuff you can do in an editor that is easier than on the command line. Not relevant to this question, though.
– TREE
Aug 31 '09 at 18:45
Your solution doesn't at all address the problem with file names containing newlines and other fancy stuff.
– glglgl
Jan 2 '15 at 9:24
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "3"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsuperuser.com%2fquestions%2f31464%2flooping-through-ls-results-in-bash-shell-script%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
7 Answers
7
active
oldest
votes
7 Answers
7
active
oldest
votes
active
oldest
votes
active
oldest
votes
Edited not to use ls where a glob would do, as @shawn-j-goff and others suggested.
Just use a for..do..done
loop:
for f in *; do
echo "File -> $f"
done
You can replace the *
with *.txt
or any other glob that returns a list (of files, directories, or anything for that matter), a command that generates a list, e.g., $(cat filelist.txt)
, or actually replace it with a list.
Within the do
loop, you just refer to the loop variable with the dollar sign prefix (so $f
in the above example). You can echo
it or do anything else to it you want.
For example, to rename all the .xml
files in the current directory to .txt
:
for x in *.xml; do
t=$(echo $x | sed 's/.xml$/.txt/');
mv $x $t && echo "moved $x -> $t"
done
Or even better, if you are using Bash you can use Bash parameter expansions rather than spawning a subshell:
for x in *.xml; do
t=${x%.xml}.txt
mv $x $t && echo "moved $x -> $t"
done
19
What if the filename has a space in it?
– Daniel A. White
Aug 28 '09 at 17:17
4
Unfortunately, as Daniel said, the code in this answer will break if any of the files or folders contains a space or newline in their name. It shows a very common misuse offor
loops, and a typical pitfall of trying to parse the output ofls
. @DanielA.White, you might consider unaccepting this answer if it wasn't helpful (or is potentially misleading), since like you said, you're acting on directories. Shawn J. Goff's answer should provide a more robust and working solution to your issue.
– slhck
Jan 23 '13 at 17:48
4
-∞ You shouldn't parsels
output, you shouldn't read output in afor
loop, you should use$()
instead of `` and Use More Quotes™. I'm sure @CoverosGene meant well, but this is just terrible.
– l0b0
Apr 17 '14 at 19:31
1
A better alternative if you really want to usels
isls -1 | while read line; do stuff; done
. At least that one won't break for whitespaces.
– Emmanuel Joubaud
Aug 1 '14 at 8:47
1
withmv -v
you don't needecho "moved $x -> $t"
– DmitrySandalov
Jan 15 '16 at 12:59
|
show 2 more comments
Edited not to use ls where a glob would do, as @shawn-j-goff and others suggested.
Just use a for..do..done
loop:
for f in *; do
echo "File -> $f"
done
You can replace the *
with *.txt
or any other glob that returns a list (of files, directories, or anything for that matter), a command that generates a list, e.g., $(cat filelist.txt)
, or actually replace it with a list.
Within the do
loop, you just refer to the loop variable with the dollar sign prefix (so $f
in the above example). You can echo
it or do anything else to it you want.
For example, to rename all the .xml
files in the current directory to .txt
:
for x in *.xml; do
t=$(echo $x | sed 's/.xml$/.txt/');
mv $x $t && echo "moved $x -> $t"
done
Or even better, if you are using Bash you can use Bash parameter expansions rather than spawning a subshell:
for x in *.xml; do
t=${x%.xml}.txt
mv $x $t && echo "moved $x -> $t"
done
19
What if the filename has a space in it?
– Daniel A. White
Aug 28 '09 at 17:17
4
Unfortunately, as Daniel said, the code in this answer will break if any of the files or folders contains a space or newline in their name. It shows a very common misuse offor
loops, and a typical pitfall of trying to parse the output ofls
. @DanielA.White, you might consider unaccepting this answer if it wasn't helpful (or is potentially misleading), since like you said, you're acting on directories. Shawn J. Goff's answer should provide a more robust and working solution to your issue.
– slhck
Jan 23 '13 at 17:48
4
-∞ You shouldn't parsels
output, you shouldn't read output in afor
loop, you should use$()
instead of `` and Use More Quotes™. I'm sure @CoverosGene meant well, but this is just terrible.
– l0b0
Apr 17 '14 at 19:31
1
A better alternative if you really want to usels
isls -1 | while read line; do stuff; done
. At least that one won't break for whitespaces.
– Emmanuel Joubaud
Aug 1 '14 at 8:47
1
withmv -v
you don't needecho "moved $x -> $t"
– DmitrySandalov
Jan 15 '16 at 12:59
|
show 2 more comments
Edited not to use ls where a glob would do, as @shawn-j-goff and others suggested.
Just use a for..do..done
loop:
for f in *; do
echo "File -> $f"
done
You can replace the *
with *.txt
or any other glob that returns a list (of files, directories, or anything for that matter), a command that generates a list, e.g., $(cat filelist.txt)
, or actually replace it with a list.
Within the do
loop, you just refer to the loop variable with the dollar sign prefix (so $f
in the above example). You can echo
it or do anything else to it you want.
For example, to rename all the .xml
files in the current directory to .txt
:
for x in *.xml; do
t=$(echo $x | sed 's/.xml$/.txt/');
mv $x $t && echo "moved $x -> $t"
done
Or even better, if you are using Bash you can use Bash parameter expansions rather than spawning a subshell:
for x in *.xml; do
t=${x%.xml}.txt
mv $x $t && echo "moved $x -> $t"
done
Edited not to use ls where a glob would do, as @shawn-j-goff and others suggested.
Just use a for..do..done
loop:
for f in *; do
echo "File -> $f"
done
You can replace the *
with *.txt
or any other glob that returns a list (of files, directories, or anything for that matter), a command that generates a list, e.g., $(cat filelist.txt)
, or actually replace it with a list.
Within the do
loop, you just refer to the loop variable with the dollar sign prefix (so $f
in the above example). You can echo
it or do anything else to it you want.
For example, to rename all the .xml
files in the current directory to .txt
:
for x in *.xml; do
t=$(echo $x | sed 's/.xml$/.txt/');
mv $x $t && echo "moved $x -> $t"
done
Or even better, if you are using Bash you can use Bash parameter expansions rather than spawning a subshell:
for x in *.xml; do
t=${x%.xml}.txt
mv $x $t && echo "moved $x -> $t"
done
edited May 8 '15 at 0:00
Michael Frank
6,38713044
6,38713044
answered Aug 28 '09 at 17:09
CoverosGeneCoverosGene
1,138106
1,138106
19
What if the filename has a space in it?
– Daniel A. White
Aug 28 '09 at 17:17
4
Unfortunately, as Daniel said, the code in this answer will break if any of the files or folders contains a space or newline in their name. It shows a very common misuse offor
loops, and a typical pitfall of trying to parse the output ofls
. @DanielA.White, you might consider unaccepting this answer if it wasn't helpful (or is potentially misleading), since like you said, you're acting on directories. Shawn J. Goff's answer should provide a more robust and working solution to your issue.
– slhck
Jan 23 '13 at 17:48
4
-∞ You shouldn't parsels
output, you shouldn't read output in afor
loop, you should use$()
instead of `` and Use More Quotes™. I'm sure @CoverosGene meant well, but this is just terrible.
– l0b0
Apr 17 '14 at 19:31
1
A better alternative if you really want to usels
isls -1 | while read line; do stuff; done
. At least that one won't break for whitespaces.
– Emmanuel Joubaud
Aug 1 '14 at 8:47
1
withmv -v
you don't needecho "moved $x -> $t"
– DmitrySandalov
Jan 15 '16 at 12:59
|
show 2 more comments
19
What if the filename has a space in it?
– Daniel A. White
Aug 28 '09 at 17:17
4
Unfortunately, as Daniel said, the code in this answer will break if any of the files or folders contains a space or newline in their name. It shows a very common misuse offor
loops, and a typical pitfall of trying to parse the output ofls
. @DanielA.White, you might consider unaccepting this answer if it wasn't helpful (or is potentially misleading), since like you said, you're acting on directories. Shawn J. Goff's answer should provide a more robust and working solution to your issue.
– slhck
Jan 23 '13 at 17:48
4
-∞ You shouldn't parsels
output, you shouldn't read output in afor
loop, you should use$()
instead of `` and Use More Quotes™. I'm sure @CoverosGene meant well, but this is just terrible.
– l0b0
Apr 17 '14 at 19:31
1
A better alternative if you really want to usels
isls -1 | while read line; do stuff; done
. At least that one won't break for whitespaces.
– Emmanuel Joubaud
Aug 1 '14 at 8:47
1
withmv -v
you don't needecho "moved $x -> $t"
– DmitrySandalov
Jan 15 '16 at 12:59
19
19
What if the filename has a space in it?
– Daniel A. White
Aug 28 '09 at 17:17
What if the filename has a space in it?
– Daniel A. White
Aug 28 '09 at 17:17
4
4
Unfortunately, as Daniel said, the code in this answer will break if any of the files or folders contains a space or newline in their name. It shows a very common misuse of
for
loops, and a typical pitfall of trying to parse the output of ls
. @DanielA.White, you might consider unaccepting this answer if it wasn't helpful (or is potentially misleading), since like you said, you're acting on directories. Shawn J. Goff's answer should provide a more robust and working solution to your issue.– slhck
Jan 23 '13 at 17:48
Unfortunately, as Daniel said, the code in this answer will break if any of the files or folders contains a space or newline in their name. It shows a very common misuse of
for
loops, and a typical pitfall of trying to parse the output of ls
. @DanielA.White, you might consider unaccepting this answer if it wasn't helpful (or is potentially misleading), since like you said, you're acting on directories. Shawn J. Goff's answer should provide a more robust and working solution to your issue.– slhck
Jan 23 '13 at 17:48
4
4
-∞ You shouldn't parse
ls
output, you shouldn't read output in a for
loop, you should use $()
instead of `` and Use More Quotes™. I'm sure @CoverosGene meant well, but this is just terrible.– l0b0
Apr 17 '14 at 19:31
-∞ You shouldn't parse
ls
output, you shouldn't read output in a for
loop, you should use $()
instead of `` and Use More Quotes™. I'm sure @CoverosGene meant well, but this is just terrible.– l0b0
Apr 17 '14 at 19:31
1
1
A better alternative if you really want to use
ls
is ls -1 | while read line; do stuff; done
. At least that one won't break for whitespaces.– Emmanuel Joubaud
Aug 1 '14 at 8:47
A better alternative if you really want to use
ls
is ls -1 | while read line; do stuff; done
. At least that one won't break for whitespaces.– Emmanuel Joubaud
Aug 1 '14 at 8:47
1
1
with
mv -v
you don't need echo "moved $x -> $t"
– DmitrySandalov
Jan 15 '16 at 12:59
with
mv -v
you don't need echo "moved $x -> $t"
– DmitrySandalov
Jan 15 '16 at 12:59
|
show 2 more comments
Using the output of ls
to get filenames is a bad idea. It can lead to malfunctioning and even dangerous scripts. This is because a filename can contain any character except /
and the null
character, and ls
does not use either of those characters as delimiters, so if a filename has a space or a newline, you will get unexpected results.
There are two very good ways of iterating over files. Here, I've used simply echo
to demonstrate doing something with the filename; you can use anything, though.
The first is to use the shell's native globbing features.
for dir in */; do
echo "$dir"
done
The shell expands */
into separate arguments that the for
loop reads; even if there is a space, newline, or any other strange character in the filename, for
will see each complete name as an atomic unit; it's not parsing the list in any way.
If you want to go recursively into subdirectories, then this won't do unless your shell has some extended globbing features (such as bash
's globstar
. If your shell doesn't have these features, or if you want to ensure that your script will work on a variety of systems, then the next option is to use find
.
find . -type d -exec echo '{}' ;
Here, the find
command will call echo
and pass it an argument of the filename. It does this once for each file it finds. As with the previous example, there is no parsing of a list of filenames; instead, a fileneame is sent completely as an argument.
The syntax of the -exec
argument looks a little funny. find
takes the first argument after -exec
and treats that as the program to run, and every subsequent argument, it takes as an argument to pass to that program. There are two special arguments that -exec
needs to see. The first one is {}
; this argument gets replaced with a filename that the previous parts of find
generates. The second one is ;
, which lets find
know this is the end of the list of arguments to pass to the program; find
needs this because you can continue with more arguments that are intended for find
and not intended for the exec'd program. The reason for the is that the shell also treats
;
specially - it represents the end of a command, so we need to escape it so that the shell gives it as an argument to find
rather than consuming it for itself; another way of getting the shell to not treat it specially is to put it in in quotes: ';'
works just as well as ;
for this purpose.
2
+1 this is definitely the way to go when you need to generate a file list and use it in a command. find -exec is limited by only being able to run a single commands. With the loop, you can pipe to your heart's content.
– MaQleod
May 10 '12 at 22:45
+1. I wish more people would usefind
. There is magic that can be done, and even the perceived limitations of-exec
can be worked around. The-print0
option is also valuable for use withxargs
.
– ghoti
Oct 17 '14 at 22:24
add a comment |
Using the output of ls
to get filenames is a bad idea. It can lead to malfunctioning and even dangerous scripts. This is because a filename can contain any character except /
and the null
character, and ls
does not use either of those characters as delimiters, so if a filename has a space or a newline, you will get unexpected results.
There are two very good ways of iterating over files. Here, I've used simply echo
to demonstrate doing something with the filename; you can use anything, though.
The first is to use the shell's native globbing features.
for dir in */; do
echo "$dir"
done
The shell expands */
into separate arguments that the for
loop reads; even if there is a space, newline, or any other strange character in the filename, for
will see each complete name as an atomic unit; it's not parsing the list in any way.
If you want to go recursively into subdirectories, then this won't do unless your shell has some extended globbing features (such as bash
's globstar
. If your shell doesn't have these features, or if you want to ensure that your script will work on a variety of systems, then the next option is to use find
.
find . -type d -exec echo '{}' ;
Here, the find
command will call echo
and pass it an argument of the filename. It does this once for each file it finds. As with the previous example, there is no parsing of a list of filenames; instead, a fileneame is sent completely as an argument.
The syntax of the -exec
argument looks a little funny. find
takes the first argument after -exec
and treats that as the program to run, and every subsequent argument, it takes as an argument to pass to that program. There are two special arguments that -exec
needs to see. The first one is {}
; this argument gets replaced with a filename that the previous parts of find
generates. The second one is ;
, which lets find
know this is the end of the list of arguments to pass to the program; find
needs this because you can continue with more arguments that are intended for find
and not intended for the exec'd program. The reason for the is that the shell also treats
;
specially - it represents the end of a command, so we need to escape it so that the shell gives it as an argument to find
rather than consuming it for itself; another way of getting the shell to not treat it specially is to put it in in quotes: ';'
works just as well as ;
for this purpose.
2
+1 this is definitely the way to go when you need to generate a file list and use it in a command. find -exec is limited by only being able to run a single commands. With the loop, you can pipe to your heart's content.
– MaQleod
May 10 '12 at 22:45
+1. I wish more people would usefind
. There is magic that can be done, and even the perceived limitations of-exec
can be worked around. The-print0
option is also valuable for use withxargs
.
– ghoti
Oct 17 '14 at 22:24
add a comment |
Using the output of ls
to get filenames is a bad idea. It can lead to malfunctioning and even dangerous scripts. This is because a filename can contain any character except /
and the null
character, and ls
does not use either of those characters as delimiters, so if a filename has a space or a newline, you will get unexpected results.
There are two very good ways of iterating over files. Here, I've used simply echo
to demonstrate doing something with the filename; you can use anything, though.
The first is to use the shell's native globbing features.
for dir in */; do
echo "$dir"
done
The shell expands */
into separate arguments that the for
loop reads; even if there is a space, newline, or any other strange character in the filename, for
will see each complete name as an atomic unit; it's not parsing the list in any way.
If you want to go recursively into subdirectories, then this won't do unless your shell has some extended globbing features (such as bash
's globstar
. If your shell doesn't have these features, or if you want to ensure that your script will work on a variety of systems, then the next option is to use find
.
find . -type d -exec echo '{}' ;
Here, the find
command will call echo
and pass it an argument of the filename. It does this once for each file it finds. As with the previous example, there is no parsing of a list of filenames; instead, a fileneame is sent completely as an argument.
The syntax of the -exec
argument looks a little funny. find
takes the first argument after -exec
and treats that as the program to run, and every subsequent argument, it takes as an argument to pass to that program. There are two special arguments that -exec
needs to see. The first one is {}
; this argument gets replaced with a filename that the previous parts of find
generates. The second one is ;
, which lets find
know this is the end of the list of arguments to pass to the program; find
needs this because you can continue with more arguments that are intended for find
and not intended for the exec'd program. The reason for the is that the shell also treats
;
specially - it represents the end of a command, so we need to escape it so that the shell gives it as an argument to find
rather than consuming it for itself; another way of getting the shell to not treat it specially is to put it in in quotes: ';'
works just as well as ;
for this purpose.
Using the output of ls
to get filenames is a bad idea. It can lead to malfunctioning and even dangerous scripts. This is because a filename can contain any character except /
and the null
character, and ls
does not use either of those characters as delimiters, so if a filename has a space or a newline, you will get unexpected results.
There are two very good ways of iterating over files. Here, I've used simply echo
to demonstrate doing something with the filename; you can use anything, though.
The first is to use the shell's native globbing features.
for dir in */; do
echo "$dir"
done
The shell expands */
into separate arguments that the for
loop reads; even if there is a space, newline, or any other strange character in the filename, for
will see each complete name as an atomic unit; it's not parsing the list in any way.
If you want to go recursively into subdirectories, then this won't do unless your shell has some extended globbing features (such as bash
's globstar
. If your shell doesn't have these features, or if you want to ensure that your script will work on a variety of systems, then the next option is to use find
.
find . -type d -exec echo '{}' ;
Here, the find
command will call echo
and pass it an argument of the filename. It does this once for each file it finds. As with the previous example, there is no parsing of a list of filenames; instead, a fileneame is sent completely as an argument.
The syntax of the -exec
argument looks a little funny. find
takes the first argument after -exec
and treats that as the program to run, and every subsequent argument, it takes as an argument to pass to that program. There are two special arguments that -exec
needs to see. The first one is {}
; this argument gets replaced with a filename that the previous parts of find
generates. The second one is ;
, which lets find
know this is the end of the list of arguments to pass to the program; find
needs this because you can continue with more arguments that are intended for find
and not intended for the exec'd program. The reason for the is that the shell also treats
;
specially - it represents the end of a command, so we need to escape it so that the shell gives it as an argument to find
rather than consuming it for itself; another way of getting the shell to not treat it specially is to put it in in quotes: ';'
works just as well as ;
for this purpose.
edited Jul 23 '14 at 13:00
answered Apr 29 '12 at 22:16
Shawn J. GoffShawn J. Goff
7881716
7881716
2
+1 this is definitely the way to go when you need to generate a file list and use it in a command. find -exec is limited by only being able to run a single commands. With the loop, you can pipe to your heart's content.
– MaQleod
May 10 '12 at 22:45
+1. I wish more people would usefind
. There is magic that can be done, and even the perceived limitations of-exec
can be worked around. The-print0
option is also valuable for use withxargs
.
– ghoti
Oct 17 '14 at 22:24
add a comment |
2
+1 this is definitely the way to go when you need to generate a file list and use it in a command. find -exec is limited by only being able to run a single commands. With the loop, you can pipe to your heart's content.
– MaQleod
May 10 '12 at 22:45
+1. I wish more people would usefind
. There is magic that can be done, and even the perceived limitations of-exec
can be worked around. The-print0
option is also valuable for use withxargs
.
– ghoti
Oct 17 '14 at 22:24
2
2
+1 this is definitely the way to go when you need to generate a file list and use it in a command. find -exec is limited by only being able to run a single commands. With the loop, you can pipe to your heart's content.
– MaQleod
May 10 '12 at 22:45
+1 this is definitely the way to go when you need to generate a file list and use it in a command. find -exec is limited by only being able to run a single commands. With the loop, you can pipe to your heart's content.
– MaQleod
May 10 '12 at 22:45
+1. I wish more people would use
find
. There is magic that can be done, and even the perceived limitations of -exec
can be worked around. The -print0
option is also valuable for use with xargs
.– ghoti
Oct 17 '14 at 22:24
+1. I wish more people would use
find
. There is magic that can be done, and even the perceived limitations of -exec
can be worked around. The -print0
option is also valuable for use with xargs
.– ghoti
Oct 17 '14 at 22:24
add a comment |
For files with spaces in you will have to make sure to quote the variable like:
for i in $(ls); do echo "$i"; done;
or, you can change the input field separator (IFS) environment variable:
IFS=$'n';for file in $(ls); do echo $i; done
Finally, depending on what you're doing, you may not even need the ls:
for i in *; do echo "$i"; done;
nice use of a subshell in the first example
– Jeremy L
Aug 28 '09 at 18:46
Why does IFS need a $ after the assignment and before the new character?
– Andy Ibanez
Nov 29 '12 at 18:57
3
Filenames can also contain newlines. Breaking onn
is insufficient. It's never a good idea to recommend parsing the output ofls
.
– ghoti
Oct 17 '14 at 22:18
add a comment |
For files with spaces in you will have to make sure to quote the variable like:
for i in $(ls); do echo "$i"; done;
or, you can change the input field separator (IFS) environment variable:
IFS=$'n';for file in $(ls); do echo $i; done
Finally, depending on what you're doing, you may not even need the ls:
for i in *; do echo "$i"; done;
nice use of a subshell in the first example
– Jeremy L
Aug 28 '09 at 18:46
Why does IFS need a $ after the assignment and before the new character?
– Andy Ibanez
Nov 29 '12 at 18:57
3
Filenames can also contain newlines. Breaking onn
is insufficient. It's never a good idea to recommend parsing the output ofls
.
– ghoti
Oct 17 '14 at 22:18
add a comment |
For files with spaces in you will have to make sure to quote the variable like:
for i in $(ls); do echo "$i"; done;
or, you can change the input field separator (IFS) environment variable:
IFS=$'n';for file in $(ls); do echo $i; done
Finally, depending on what you're doing, you may not even need the ls:
for i in *; do echo "$i"; done;
For files with spaces in you will have to make sure to quote the variable like:
for i in $(ls); do echo "$i"; done;
or, you can change the input field separator (IFS) environment variable:
IFS=$'n';for file in $(ls); do echo $i; done
Finally, depending on what you're doing, you may not even need the ls:
for i in *; do echo "$i"; done;
answered Aug 28 '09 at 17:26
johnjohn
45726
45726
nice use of a subshell in the first example
– Jeremy L
Aug 28 '09 at 18:46
Why does IFS need a $ after the assignment and before the new character?
– Andy Ibanez
Nov 29 '12 at 18:57
3
Filenames can also contain newlines. Breaking onn
is insufficient. It's never a good idea to recommend parsing the output ofls
.
– ghoti
Oct 17 '14 at 22:18
add a comment |
nice use of a subshell in the first example
– Jeremy L
Aug 28 '09 at 18:46
Why does IFS need a $ after the assignment and before the new character?
– Andy Ibanez
Nov 29 '12 at 18:57
3
Filenames can also contain newlines. Breaking onn
is insufficient. It's never a good idea to recommend parsing the output ofls
.
– ghoti
Oct 17 '14 at 22:18
nice use of a subshell in the first example
– Jeremy L
Aug 28 '09 at 18:46
nice use of a subshell in the first example
– Jeremy L
Aug 28 '09 at 18:46
Why does IFS need a $ after the assignment and before the new character?
– Andy Ibanez
Nov 29 '12 at 18:57
Why does IFS need a $ after the assignment and before the new character?
– Andy Ibanez
Nov 29 '12 at 18:57
3
3
Filenames can also contain newlines. Breaking on
n
is insufficient. It's never a good idea to recommend parsing the output of ls
.– ghoti
Oct 17 '14 at 22:18
Filenames can also contain newlines. Breaking on
n
is insufficient. It's never a good idea to recommend parsing the output of ls
.– ghoti
Oct 17 '14 at 22:18
add a comment |
If you have GNU Parallel http://www.gnu.org/software/parallel/ installed you can do this:
ls | parallel echo {} is in this dir
To rename all .txt to .xml:
ls *.txt | parallel mv {} {.}.xml
Watch the intro video for GNU Parallel to learn more:
http://www.youtube.com/watch?v=OpaiGYxkSuQ
add a comment |
If you have GNU Parallel http://www.gnu.org/software/parallel/ installed you can do this:
ls | parallel echo {} is in this dir
To rename all .txt to .xml:
ls *.txt | parallel mv {} {.}.xml
Watch the intro video for GNU Parallel to learn more:
http://www.youtube.com/watch?v=OpaiGYxkSuQ
add a comment |
If you have GNU Parallel http://www.gnu.org/software/parallel/ installed you can do this:
ls | parallel echo {} is in this dir
To rename all .txt to .xml:
ls *.txt | parallel mv {} {.}.xml
Watch the intro video for GNU Parallel to learn more:
http://www.youtube.com/watch?v=OpaiGYxkSuQ
If you have GNU Parallel http://www.gnu.org/software/parallel/ installed you can do this:
ls | parallel echo {} is in this dir
To rename all .txt to .xml:
ls *.txt | parallel mv {} {.}.xml
Watch the intro video for GNU Parallel to learn more:
http://www.youtube.com/watch?v=OpaiGYxkSuQ
edited Apr 30 '12 at 0:49
whitequark
13.1k53950
13.1k53950
answered Aug 22 '10 at 20:02
Ole TangeOle Tange
2,2801823
2,2801823
add a comment |
add a comment |
Just to add to CoverosGene's answer, here is a way to list just the directory names:
for f in */; do
echo "Directory -> $f"
done
add a comment |
Just to add to CoverosGene's answer, here is a way to list just the directory names:
for f in */; do
echo "Directory -> $f"
done
add a comment |
Just to add to CoverosGene's answer, here is a way to list just the directory names:
for f in */; do
echo "Directory -> $f"
done
Just to add to CoverosGene's answer, here is a way to list just the directory names:
for f in */; do
echo "Directory -> $f"
done
answered Feb 11 '15 at 14:36
n3on3o
1413
1413
add a comment |
add a comment |
Why not set IFS to a newline, then capture the output of ls
in an array? Setting IFS to newline should resolve issues with funny characters in file names; using ls
can be nice because it has a built-in sort facility.
(In testing I was having trouble setting IFS to n
but setting it to newline backspace works, as suggested elsewhere here):
E.g. (assuming desired ls
search pattern passed in $1
):
SAVEIFS=$IFS
IFS=$(echo -en "nb")
FILES=($(/bin/ls "$1"))
for AFILE in ${FILES[@]}
do
... do something with a file ...
done
IFS=$SAVEIFS
This is especially handy on OS X, e.g., to capture a list of files sorted by creation date (oldest to newest), the ls
command is ls -t -U -r
.
2
Filenames can also contain newlines, and they often do when users are permitted to name their own files. Breaking onn
is insufficient. The only valid solutions use a for loop with pathname expansion, orfind
.
– ghoti
Oct 17 '14 at 22:21
The only reliable way to transfer a list of file names is to separate them with a NUL character, as this is the only one definitely not contained in a file path.
– glglgl
Jan 2 '15 at 9:23
add a comment |
Why not set IFS to a newline, then capture the output of ls
in an array? Setting IFS to newline should resolve issues with funny characters in file names; using ls
can be nice because it has a built-in sort facility.
(In testing I was having trouble setting IFS to n
but setting it to newline backspace works, as suggested elsewhere here):
E.g. (assuming desired ls
search pattern passed in $1
):
SAVEIFS=$IFS
IFS=$(echo -en "nb")
FILES=($(/bin/ls "$1"))
for AFILE in ${FILES[@]}
do
... do something with a file ...
done
IFS=$SAVEIFS
This is especially handy on OS X, e.g., to capture a list of files sorted by creation date (oldest to newest), the ls
command is ls -t -U -r
.
2
Filenames can also contain newlines, and they often do when users are permitted to name their own files. Breaking onn
is insufficient. The only valid solutions use a for loop with pathname expansion, orfind
.
– ghoti
Oct 17 '14 at 22:21
The only reliable way to transfer a list of file names is to separate them with a NUL character, as this is the only one definitely not contained in a file path.
– glglgl
Jan 2 '15 at 9:23
add a comment |
Why not set IFS to a newline, then capture the output of ls
in an array? Setting IFS to newline should resolve issues with funny characters in file names; using ls
can be nice because it has a built-in sort facility.
(In testing I was having trouble setting IFS to n
but setting it to newline backspace works, as suggested elsewhere here):
E.g. (assuming desired ls
search pattern passed in $1
):
SAVEIFS=$IFS
IFS=$(echo -en "nb")
FILES=($(/bin/ls "$1"))
for AFILE in ${FILES[@]}
do
... do something with a file ...
done
IFS=$SAVEIFS
This is especially handy on OS X, e.g., to capture a list of files sorted by creation date (oldest to newest), the ls
command is ls -t -U -r
.
Why not set IFS to a newline, then capture the output of ls
in an array? Setting IFS to newline should resolve issues with funny characters in file names; using ls
can be nice because it has a built-in sort facility.
(In testing I was having trouble setting IFS to n
but setting it to newline backspace works, as suggested elsewhere here):
E.g. (assuming desired ls
search pattern passed in $1
):
SAVEIFS=$IFS
IFS=$(echo -en "nb")
FILES=($(/bin/ls "$1"))
for AFILE in ${FILES[@]}
do
... do something with a file ...
done
IFS=$SAVEIFS
This is especially handy on OS X, e.g., to capture a list of files sorted by creation date (oldest to newest), the ls
command is ls -t -U -r
.
edited Oct 22 '12 at 1:09
Indrek
20.6k117484
20.6k117484
answered May 10 '12 at 22:36
jetsetjetset
1294
1294
2
Filenames can also contain newlines, and they often do when users are permitted to name their own files. Breaking onn
is insufficient. The only valid solutions use a for loop with pathname expansion, orfind
.
– ghoti
Oct 17 '14 at 22:21
The only reliable way to transfer a list of file names is to separate them with a NUL character, as this is the only one definitely not contained in a file path.
– glglgl
Jan 2 '15 at 9:23
add a comment |
2
Filenames can also contain newlines, and they often do when users are permitted to name their own files. Breaking onn
is insufficient. The only valid solutions use a for loop with pathname expansion, orfind
.
– ghoti
Oct 17 '14 at 22:21
The only reliable way to transfer a list of file names is to separate them with a NUL character, as this is the only one definitely not contained in a file path.
– glglgl
Jan 2 '15 at 9:23
2
2
Filenames can also contain newlines, and they often do when users are permitted to name their own files. Breaking on
n
is insufficient. The only valid solutions use a for loop with pathname expansion, or find
.– ghoti
Oct 17 '14 at 22:21
Filenames can also contain newlines, and they often do when users are permitted to name their own files. Breaking on
n
is insufficient. The only valid solutions use a for loop with pathname expansion, or find
.– ghoti
Oct 17 '14 at 22:21
The only reliable way to transfer a list of file names is to separate them with a NUL character, as this is the only one definitely not contained in a file path.
– glglgl
Jan 2 '15 at 9:23
The only reliable way to transfer a list of file names is to separate them with a NUL character, as this is the only one definitely not contained in a file path.
– glglgl
Jan 2 '15 at 9:23
add a comment |
This is how I do it, but there are probably more efficient ways.
ls > filelist.txt
while read filename; do echo filename: "$filename"; done < filelist.txt
6
Stick to pipes in place of the file: >ls | while read i; do echo filename: $i; done
– Jeremy L
Aug 28 '09 at 18:46
Cool. I should say that you can also use $EDITOR filelist.txt in between the two commands. Lots of stuff you can do in an editor that is easier than on the command line. Not relevant to this question, though.
– TREE
Aug 31 '09 at 18:45
Your solution doesn't at all address the problem with file names containing newlines and other fancy stuff.
– glglgl
Jan 2 '15 at 9:24
add a comment |
This is how I do it, but there are probably more efficient ways.
ls > filelist.txt
while read filename; do echo filename: "$filename"; done < filelist.txt
6
Stick to pipes in place of the file: >ls | while read i; do echo filename: $i; done
– Jeremy L
Aug 28 '09 at 18:46
Cool. I should say that you can also use $EDITOR filelist.txt in between the two commands. Lots of stuff you can do in an editor that is easier than on the command line. Not relevant to this question, though.
– TREE
Aug 31 '09 at 18:45
Your solution doesn't at all address the problem with file names containing newlines and other fancy stuff.
– glglgl
Jan 2 '15 at 9:24
add a comment |
This is how I do it, but there are probably more efficient ways.
ls > filelist.txt
while read filename; do echo filename: "$filename"; done < filelist.txt
This is how I do it, but there are probably more efficient ways.
ls > filelist.txt
while read filename; do echo filename: "$filename"; done < filelist.txt
answered Aug 28 '09 at 17:28
TREETREE
7171614
7171614
6
Stick to pipes in place of the file: >ls | while read i; do echo filename: $i; done
– Jeremy L
Aug 28 '09 at 18:46
Cool. I should say that you can also use $EDITOR filelist.txt in between the two commands. Lots of stuff you can do in an editor that is easier than on the command line. Not relevant to this question, though.
– TREE
Aug 31 '09 at 18:45
Your solution doesn't at all address the problem with file names containing newlines and other fancy stuff.
– glglgl
Jan 2 '15 at 9:24
add a comment |
6
Stick to pipes in place of the file: >ls | while read i; do echo filename: $i; done
– Jeremy L
Aug 28 '09 at 18:46
Cool. I should say that you can also use $EDITOR filelist.txt in between the two commands. Lots of stuff you can do in an editor that is easier than on the command line. Not relevant to this question, though.
– TREE
Aug 31 '09 at 18:45
Your solution doesn't at all address the problem with file names containing newlines and other fancy stuff.
– glglgl
Jan 2 '15 at 9:24
6
6
Stick to pipes in place of the file: >
ls | while read i; do echo filename: $i; done
– Jeremy L
Aug 28 '09 at 18:46
Stick to pipes in place of the file: >
ls | while read i; do echo filename: $i; done
– Jeremy L
Aug 28 '09 at 18:46
Cool. I should say that you can also use $EDITOR filelist.txt in between the two commands. Lots of stuff you can do in an editor that is easier than on the command line. Not relevant to this question, though.
– TREE
Aug 31 '09 at 18:45
Cool. I should say that you can also use $EDITOR filelist.txt in between the two commands. Lots of stuff you can do in an editor that is easier than on the command line. Not relevant to this question, though.
– TREE
Aug 31 '09 at 18:45
Your solution doesn't at all address the problem with file names containing newlines and other fancy stuff.
– glglgl
Jan 2 '15 at 9:24
Your solution doesn't at all address the problem with file names containing newlines and other fancy stuff.
– glglgl
Jan 2 '15 at 9:24
add a comment |
Thanks for contributing an answer to Super User!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsuperuser.com%2fquestions%2f31464%2flooping-through-ls-results-in-bash-shell-script%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown