1

I only know little about bash (or any Linux shell - but I just use bash). I'm used to using for %c in (*.*) do {something} in cmd in Windows, and I really feel inconvenient without it. I tried wine cmd, but it wouldn't interpret Linux commands (which I need for the {something}) at all, which is not I want. Any help?

In Windows, for %c in (*.*) do {something} runs {something} once per file (ignoring directories). {something} can refer to %c for the current iteration's file name; other modifiers allow the file's path to be extracted, or the file's name without its path, the file's extension, attributes, modification date/time, size... for /d %c in (*.*) ... will process directories instead of files. In both cases *.* is equivalent to * in Unix-style systems, it matches all non-hidden files or directories, even those with no . in their name.

P.S. I know that the cmd "for" has many usages which may not be covered in one command in bash, but I just want to know the equivalent of the command mentioned above.

  • 2
    When asking here, don't assume we know what the windows shell commands are. You need to explain what they are doing. I am guessing that the one you show will iterate over all files and directories in the current directory and save them as the variable %c but is it recursive? Is that actually what it does? Remember that we're *nix people and don't know Windows. – terdon Feb 10 '15 at 15:29
  • 1
    Some of us do ;-). for %c in (*.*) do {something} runs {something} once per file (ignoring directories). {something} can refer to %c for the current iteration's file name; other modifiers allow the file's path to be extracted, or the file's name without its path, the file's extension, attributes, modification date/time, size... – Stephen Kitt Feb 10 '15 at 15:36
  • 2
    @StephenKitt great, please edit and include it then. I'm sure some of us do, I was just making the point to a new user that they shouldn't expect us to since most of us (many anyway) don't :). – terdon Feb 10 '15 at 15:47
  • @terdon Point taken, thanks ;-). I've edited the question. – Stephen Kitt Feb 10 '15 at 16:16
5
for file in *; do
  echo "$file"
done

This usually does not match "hidden" files (names beginning with a dot) though.

For matching the hidden files, too, you need this before:

shopt -s dotglob

If you want to skip directories (and probably anything "strange") then you have to detect them in the loop; pattern matching (the *) doesn't care about the type of object:

for file in *; do
  test -f "$file" || continue
  echo "$file"
done

Symbolic links are a special case. They are considered if they link to a file but the file may be in another directory. To ignore symlinks:

for file in *; do
  test -f "$file" || continue
  test -L "$file" && continue
  echo "$file"
done
  • 1
    for file in * also matches folders, which for %c in (*.*) won't do in Windows. – Stephen Kitt Feb 10 '15 at 15:06
  • @StephenKitt: I probably shouldn't ask, but "how do you get files and directories in Windows?". – Jonathan Leffler Feb 10 '15 at 15:16
  • for /d %c in (*.*) matches all directories (even those without a ....). I don't think you can handle both files and directories in one command. – Stephen Kitt Feb 10 '15 at 15:20
  • @mikeserv I would be more careful with statements like "That shopt stuff isn't helping anybody". I didn't tell the questioner to ignore symlinks; I told him how to do that. I don't know what makes more sense in his situation. He's coming from the Windows world and thus probably not familiar with symlinks. – Hauke Laging Feb 10 '15 at 16:58
  • Fair points, Hauke. Please accept my apology - I removed the link thing from the comment because I read it again and saw where you explained that. The shopt thing though - it isn't one command in that way, and, what's more, alters shell state without recommending how to save it then afterward restore it. And it isn't necessary here either, because you test the globs anyway. – mikeserv Feb 10 '15 at 17:11
1

The pattern matching behavior you describe is - according to this technet blog - a grandfathered bug:


...you'll also discover that *.* is the same as * by itself.

In addition ... if you typed DIR .TXT, the command prompt acted as if you had typed DIR *.TXT... This behavior was probably ... not intentional, but it was an accident that some people came to rely upon. When we fixed the bug in Windows 95, more than one person complained that their DIR .TXT command wasn't working.

The FCB matching algorithm was abandoned during the transition to Win32 since it didn't work with long file names. Long file names can contain multiple dots, and of course files can be longer than eleven characters, and there can be more than eight characters before the dot. But some quirks of the FCB matching algorithm persist into Win32 because they have become idiom.

For example, if your pattern ends in .*, the .* is ignored. Without this rule, the pattern *.* would match only files that contained a dot, which would break probably 90% of all the batch files on the planet, as well as everybody's muscle memory, since everybody running Windows NT 3.1 grew up in a world where *.* meant all files.

As another example, a pattern that ends in a dot doesn't actually match files which end in a dot; it matches files with no extension. And a question mark can match zero characters if it comes immediately before a dot.


This may not differ too terribly from the origin of the Unix precedent for dot files, as Rob Pike notes in his blog:


Long ago, as the design of the Unix file system was being worked out, the entries . and .. appeared, to make navigation easier. I'm not sure but I believe .. went in during the Version 2 rewrite, when the file system became hierarchical (it had a very different structure early on). When one typed ls, however, these files appeared, so either Ken or Dennis added a simple test to the program. It was in assembler then, but the code in question was equivalent to something like this:

if (name[0] == '.') continue;

This statement was a little shorter than what it should have been, which is

if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;

...but hey, it was easy...

I'm pretty sure the concept of a hidden file was an unintended consequence. It was certainly a mistake.

(For those who object that dot files serve a purpose, I don't dispute that but counter that it's the files that serve the purpose, not the convention for their names. They could just as easily be in $HOME/cfg or $HOME/lib, which is what we did in Plan 9, which had no dot files. Lessons can be learned.)


Since then, though, the . dot files came to be associated with the standard shell . utility, and from there just with user-specific plain-text configuration files.

You won't typically find dot files anywhere but a user's ~ because that is really the only place they would make sense - a user shouldn't be keeping any of their files anywhere else in the filesystem, anyway. When working on files in one's home directory it would be unfortunate if a user were to accidentally remove some or all of their configuration files - but it would be even more unfortunate if - when in ~ or any other directory - a loop were to unintentionally affect . - the current directory - or (worse still) .. - the parent directory. POSIX has this to say about .:


If a filename begins with a period ., the period shall be explicitly matched by using a period as the first character of the pattern or immediately following a /slash character. The leading period shall not be matched by:

  1. The *asterisk or ?question-mark special characters

  2. A [bracket] expression containing a non-matching list, such as [!a], a range expression, such as "[%-0]", or a character class expression, such as [[:punct:]]

    • It is unspecified whether an explicit period in a bracket expression matching list, such as [.abc], can match a leading period in a filename.

Still, though, it is possible to loop over all files in the current directory - dot files or not - in a single loop simply enough. For example:

set -- .* *
while until [ -f "$1" ] ||
            [ -z "$1" ] &&
            break "$((!$#+1))"
      do    shift;   done
do    printf %s\\n  "$1"
      shift
done

The above prints all regular files in the current directory in dash, bash, ksh, zsh, mksh, posh, and yash. It is even more akin to the Windows FOR loop than is the typical shell for in that this doesn't set any permanent shell variables - just like a cmd FOR it processes its arguments and when it is through with them their names no longer hold any value. In a typical shell for the last value in $var in for var in ... remains.

It doesn't have to be like that:

set -- .* * ''
while [ -n "$1" ]  || ! shift
do    [ -f "$1" ]  &&
      set -- "$@" "$1"
      shift
done
printf %s\\n "$@"

...accomplishes the same goal just as portably - printing all regular files in the current directory - but it loops over the arguments once first and prunes those which are not regular files. The advantage, though, is that afterward the shell's arg "$@" array still holds the whole list, and from that point on you can do:

for f do : something w/ "$f"; done

...as many times as you please on the same array. But you also get the count in "$#" and all args concatenated into a single string in "$*" and each addressable singly in sorted order in "$1" ... "${10}" and so on.

Those are typically not the kind of things you can do w/ Win32 cmd.

-1

if you interesting in for loop learning

syntax -

for i in $(command with iterable elements); do command $i; done

like -

for i in $(echo 1 2 3 4 5); do echo $i; done

if you interesting in search command -

find . -name "*"

with depth level -

find . -maxdepth 1 -name "*"
find . -maxdepth 2 -name "*"

maybe it more useful for you

  • 1
    A couple of points: i) for i in $() is rarely a good idea (it breaks on whitespace), command | while read is generally safer ii) find . -name "*" is unnecessary, it is the same as find alone which expands to find . -name "*". – terdon Feb 10 '15 at 15:51
  • @peter agree. "*" - only example which allows us to understand * = all. my answer not very good it's maybe bad, but i think it's... useful experience to try. sorry – dmgl Feb 10 '15 at 16:16
  • @terdon - it doesn't break on whitespace - it breaks on $IFS. You can do it safely - and well - but you have to be very sure about what you're doing. I don't this answer demonstrates that at all. – mikeserv Feb 10 '15 at 16:44

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.