Edit: SOLVED. Thank you all for your incredible insights! All of you helped me improve my code and knowledge! Special thanks to @Quibblekrust@thelemmy.club who just NAILED it. :)
I’m playing around with Bash just to learn.
LIST=$(ls); for i in $LIST; do echo "I found one!"; done
The variable “i” could literally be anything, as long as it doesn’t have a special meaning for Bash, in which case I’d have to escape it, right? Anyway, my real question is: how does do (or rather the whole for-expression) know that “i” here means “for every line/item that ls outputs”? The above one liner works great and writes “I found one!” the number of times corresponding to the number of lines or items that ls outputs. But I would like to understand why it worked…
I’m a complete beginner at both Bash and C, but I understand some basic concepts.


for loops
Your code executes
lsand records the results in a variable. The result is some text, a string of characters. (We call them “strings” andiis now a string variable.) Among the characters in a string variable might be spaces, tabs, or new line characters. I mention this because the special variable IFS is used byforloops, and it contains exactly one space, tab, and new line by default.When you call
forwith a string as the input, it splits the string into units by splitting on each character inIFS. That is, it splits the big string into individual parts by splitting at each space, tab and new line. So this creates an array which is what is looped over. Each word in turn is assigned to your looping variable and then the code after thedois executed once per word.(“word” has a sort of a special meaning here. When I say word, I mostly just mean a string that has no spaces in it. When you read text in English, there are words. They’re strings of characters separated by spaces. But words can also be separated by tabs, new lines, commas, semicolons, or whatever, but not by default when using
for! You have to modifyIFSto add those characters if you want them to be considered word separators.)So, if any of the file system entries returned by
lshave spaces in them, your loop is going to create more outputs than there are file system entries in the current directory.For example:
file one.txt file two.txt my photo.jpg notes (final).md a b c d.txtThat would cause like 12 loops and 12 outputs in your code despite there only being five files.
If you instead overwrite
IFSbefore running your loop, and only assign a single new line to the variable, then your loop will only be over the actual lines of the input text. Like this:IFS=$'\n'and then use your exact code above. Using my example of five files, this code will now only produce 5 outputs, not 12.
You can assign whatever characters you want to
IFS.(I have not tested any of this code, or examples.)
Variable names
The loop variable name
iis just an identifier. Any valid variable name would work except you can’t use the reserved names like $1, $2 or any keywords as names. Also, there’s no way to escape an identifier. They are just literal names.You also don’t want to use any built-in variable names or else you’ll overwrite their values for the duration of the current session. Bash will happily let you use them as your looping variable, but the rest of your code might have undesirable results. Variable names like
IFS, for instance. :DThis about the IFS variable was eye opening! Thank you SO much! This is exactly what I was trying to understand, namely, how on earth the for-loop is smart enough to understand how to count when I haven’t specified a numerical interval (as I do in for instance C when I practice that). This just solved it all. Thanks! Now I also understand why my code gave me excessive outputs when I changed
lsintols -l. The IFS variable made the for-loop count every single blank space!!! :DFor maximum pedantry, it may be worth mentioning that filenames in typical Linux file systems can contain newline characters.