Find is a beautiful tool
eriwen.com
eriwen.com
Following is slightly off-topic but for searching inside files like
find . -name "*.css" -exec grep -l "#content" {} \;
try `ack` instead ack --css '#content'
Above command will recursively search for '#content' in all css files under the current directory. It is faster than grep and does automatically exclude your hidden version control folders. No need to mess with `--exclude`. I really stopped using grep at all.
ack can be found here: http://betterthangrep.com/ > ack --type-add haml=.haml
ack: --type-add: Type "haml" does not exist, creating with ".haml" ...
ack: No regular expression found.
followed by being unable to use the type, and not showing it in --help types. Am I using it incorrectly? I got it via `brew install ack`, so that could be the problem as well.edit: --type-set=haml=.haml in a .ackrc file works. Though I may have to tweak it to look in the current directory as well - not all projects need the same settings, and in `~` it's not git-able.
export ACKRC=./.ackrc.local
as an env variable, and then create that file where-ever you
need to override default behaviour, otherwise it'll use~/.ackrc
The downside to doing it this way is that you only get one or the other, you can't (afaik) load ~/.ackrc, and then only override certain aspects of it locally. Also, you're constrained to running it from the basedir of your project. (You might be able to overcome this with a hack based on PROMPT_COMMAND (for bash), or a precmd/preexec function for zsh), which redefines ACKRC appropriately.
Regarding the 'gitability' of it, there's no reason why you can't have a ~/.dotfiles/ackrc, with a symlink to ~ That's how I manage my dotfiles anyway, along with a makefile to create any missing symlinks.
Something like http://kitenet.net/~joey/code/etckeeper/ might work out even better, although I've never really found time to play with it.
I'm not arguing about the value of 'find' (indeed, this recent article well explains its usefulness: http://news.ycombinator.com/item?id=2698180). I just fumble every time I try to use it because it seems to contravene conventions. I welcome any historical context or clarification if my expectations are in fact flawed.
(Though you should probably always think twice before using 'destroy disk' anyway!)
HISTORY
A find command appeared in Version 1 AT&T UNIX.Actually, it does conform to unix conventions:
find [OPTIONS] [PATHS] [EXPRESSION]
Where options are "-PLHDO" in the GNU version. The elements of EXPRESSION may be introduced by a dash, but they're not really options. Together, they specify a little program to evaluate on every path. The program could have easily taken the form 'type f a name foo' instead of '-type f -a -name foo'. The only issue would be recognizing the end of PATHS arguments and the beginning of EXPRESSION arguments...perhaps that's why the dash is used? It does seem like OPTIONS EXPRESSION [--] PATHS would be easier and less ambiguous to parse.> Also, what is the point of the '-print' option? Pretty much every other utility prints results to stdout by default.
find does print to stdout by default. Perhaps -print was added later to distinguish the default mode from -print0, which is common when pairing with xargs.
> I welcome any historical context or clarification if my expectations are in fact flawed.
I was kind of hoping the recently rehabilitated UNIX 1972 sources would shed some light on your questions. Unfortunately, find is present but only as a PDP-11 executable [1].
[1] http://code.google.com/p/unix-jun72/source/browse/#svn%2Ftru...
> added later to distinguish the default mode from -print0,
> which is common when pairing with xargs.
find ... -print0 | xargs -0 $cmd
is essential if you don't want all sorts of terrible things happening because a filename happens to contain a space or some sort of shell metachar.See http://en.wikipedia.org/wiki/Xargs#The_separator_problem for the details.
Personally, I use
find ... -exec $cmd '{}' \; # calls $cmd once for each match
find ... -exec $cmd '{}' + # calls $cmd with as matches as possible - minimising number of invocations of $cmd.
I think this is either reasonably new, or not entirely standard, since I've used versions that don't have it. -ok $cmd \;
is also pretty neat - same as exec, but asks for confirmation first. (find-cmd '(prune (name ".svn" ".git" ".CVS"))
'(and (or (name "*.pl" "*.pm" "*.t")
(mtime "+1"))
(fstype "nfs" "ufs"))))
becomes: "find '/home/phil/' \\( \\( -name '.svn' -or -name '.git' -or
-name '.CVS' \\) -prune -or -true \\) \\( \\( \\( -name '*.pl'
-or -name '*.pm' -or -name '*.t' \\) -or -mtime '+1' \\) -and \\(
-fstype 'nfs' -or -fstype 'ufs' \\) \\)"I remember finally 'getting it', and asking myself why anyone would want a computer where you don't have such tools.
My main gripe with the shell at this point is inconsistencies in syntax from one command to another, especially when it means different dialects of regex.
Check out Perl. Part of its raison d'être is to deal with this very frustration.
That said, there are some nice constructs such as:
q(some string); # same as a single-quoted string
qq(another $string); # double-quoted string.
qx(); # backticks, or $() in bash.f '.css' # regular recursive find for css files
f '.css' '#content' # find and grep
fgu '*.css' '#content' # give a listing of unique css files containing '#content'
If anyone wants to take a peek, the f, fg, and fgu functions are on github (https://github.com/bartvandendriessche/fish-nuggets/tree/mas...).
http://scott.wiersdorf.org/blarney/071024a.html
tl;dr version:
find examines each entry in a directory hierarchy; if the entry (be it a regular file, symlink, directory, device, or whatever) matches the given criteria, it will be printed. If no criteria are given, it’s an automatic match (and will be printed).
find evaluates its expressions in the order you specify them; by carefully choosing the order of the expressions, you can shave tons of time off the cost of the search and get your wanted results quicker.
As is true with most programming endeavours, a little investment up front in crafting the expressions will yield better results later. If you’re only running a find operation once, maybe you don’t want to take too much time fussing over saving a few syscalls, but if you will be running find frequently (e.g., as part of a cron, or some other regular occurance), do your disk a favor and let find skip as much as possible.
To just find a file:
du -a . | grep filename
To find every file containing the string "foo" (the awk is necessary to remove file sizes, you could combine du and awk in a shell function if desired): for (i in `{du -a . | awk '{ print $2 }'}) {
grep 'foo' $i
}
Really, the for loop above takes care of a few of find's options. Why do you need both "-exec" and "-delete"? Exec is far more versatile, you can just exec "rm" on the files. Except that as I've shown above, it's already quite easy to execute a program for every file that matches some criteria, and you don't have to learn the dozens of options used in find--you just apply the shell programming knowledge you should (hopefully) already have.I simply do find <path> to give me a list of all the paths under the current directory and then pipe that into grep.
That's because they're not options, they're predicates. A sequence of predicates to be precise (they execute strictly in-order and are short-circuited: the first predicate failure will stop the whole evaluation unless you're using `-or`). They're closer to `test`'s operator than to `grep`'s switches.
Minimalism at the expense of helping the user get the job done isn't the right tradeoff.
du -a | cut -f2
is a less sledgehammery approach than requiring awk. I don't recognise the OP's shell syntax (unless it's intended more as pseudo-code), but I think at least in bash, you'd have problems with embedded whitespace in filenames.Find and Xargs have the option nul-separated output records specifically for this reason.
Also in Plan 9, we typically don't have spaces in file names, but I think when we do they come out quoted... as far as I know there's no xargs.
Find absolutely is a beautiful tool. It does one and only one thing: apply predicates to the file system.
> du -a . | grep filename
Except that's going to match on the dirname content as well, whereas `find -type f -name filename` will only match the actual file name.
And you're going to get complete junk in the first section of each line which you'll have to cleanup afterwards.
> (the awk is necessary to remove file sizes, you could combine du and awk in a shell function if desired)
Right, whereas if you use `find` it gives exactly what you want (or need)
> Why do you need both "-exec" and "-delete"?
You don't need both, but -delete provides additional clarity and forces depth-first traversal.
> Except that as I've shown above, it's already quite easy to execute a program for every file that matches some criteria
Except your examples are all broken since you're not using `basename`. Turns out it's not that easy, and it soon becomes complete shell-soup.
> and you don't have to learn the dozens of options used in find
They're not actually options, they're predicates.
find | while read i; do grep 'foo' $i; doneAnd what are you going to do when you want to find all files modified more then 32 hours ago?
-delete is necessary because otherwise there is a race condition which can allow a regular user to make root delete any file.
See: http://news.ycombinator.com/item?id=2699050
You should not use -exec, you should use -execdir, -exec is not safe.
find . -name "*.gif" -exec convert "{}" "{}.png" \;
Will convert every GIF in the current directory (and its descendants) into a PNG, assuming imagemagick's convert is on your path. Is there a better way? Not that I know of._yawns_
Can we please get past statements like these? They are _so 90's.
See Powershell: http://en.wikipedia.org/wiki/Windows_PowerShell
The reason many people don't know to much about PowerShell is that cmd.exe is still the default windows shell, even after 25 years. PowerShell has been around for 4 of those, and only in the last two years did it start shipping by default with a Microsoft OS.
You're absolutely correct.
You're absolutly correct. I know that I'd pay $30 for a decent terminal program. I live in ipython / PowerShell. I want things like tabs, syntax coloring, copy / paste, automatic hyperlink generation for urls and file links.
find . -name "*.css"
is: ls -d **/*.css
in zsh. Other examples, all derived from the article: find . -type f -name "*.css" ==> ls **/*.css(.)
find . -name "*.css" -exec grep -l "#content" {} \;
==> grep -l "#content" **/*.css
find . -ctime -1 -type f ==> ls **/*(.c-1)
find . \! -path "*CVS*" -type f -name "*.css"
==> ls **/*.css(.e{'echo $REPLY | grep -v "CVS"'})
More can be found in the "Glob Qualifiers" section of the zshexpn(1) man page. -maxdepth 1 will only search the current directory
-maxdepth 2 will search 1 folder deep
etcI use this all the time
First one nukes everything including the git repo
Second does what I expect
Both do what you expect once you understand `-name` and `-delete` are predicates (not switches) and find short-cuts.
Once bitten, etc...
find -name "foo*" -or -name "*bar" -print
which should actually be find \( -name "foo*" -or -name "*bar" \) -print
to do what you might expect (print things matching beginning with foo or ending with bar)Without the parens, it gets interpreted as
-name "foo*" -or ( -name "*bar" -and -print ),
and hence only prints things that match the latter predicate.