grep1-2 Exercise 2: What -I Hides, and Which Files Lack a TODO =============================================================== (Run from an empty folder. The first block builds the practice project from the chapter; your own tree can skip it.) mkdir -p project/src project/notes project/build project/logs project/data project/.git "project/my notes" cd project printf '# Demo project\nTODO write the install guide\nRun app.py to start\n' > README.md printf 'import os\n# TODO handle errors\ndef main():\n print("hello")\n' > app.py printf 'def helper():\n # TODO remove this\n return 1\n' > src/util.py printf '// TODO rename\nconsole.log("hi");\n' > src/main.js printf 'buy milk\nTODO call the plumber\n' > notes/todo.txt printf '/* generated */ var a = 1; // TODO generated noise\n' > build/bundle.js printf 'INFO started\nERROR something broke\n' > logs/app.log printf 'data\0TODO inside a binary file\n' > data/image.bin printf 'API_KEY=abc123\npassword=hunter2\n' > .env printf '[core]\n\trepositoryformatversion = 0\n# TODO placeholder inside .git\n' > .git/config printf 'TODO book the room\npassword reminder: change it\n' > "my notes/meeting notes.txt" # --- the exercise --- echo "Files that contain TODO but are hidden by -I:" comm -13 <(grep -rIl TODO . | sort) <(grep -rl TODO . | sort) echo echo "Files with no TODO at all (the .git folder skipped):" grep -rL TODO --exclude-dir=.git . echo echo "The same with -I: binary files now count as 'no match', so the binary file is listed as well:" grep -rIL TODO --exclude-dir=.git . Output: Files that contain TODO but are hidden by -I: ./data/image.bin Files with no TODO at all (the .git folder skipped): ./.env ./logs/app.log The same with -I: binary files now count as 'no match', so the binary file is listed as well: ./.env ./data/image.bin ./logs/app.log WHY THIS WORKS AS AN ANSWER --------------------------- comm -13 prints the lines that are only in the second list. The second list is every file with a match, the first is every text file with a match (-I), so what is left is the files that match but are binary: data/image.bin. Both lists go through sort because comm needs sorted input, and the <( ... ) form feeds a command's output to comm as if it were a file (bash, not plain sh). -L lists files without the word. Without -I, the binary file DOES contain TODO (inside its bytes), so it is correctly not listed: the answer is .env and logs/app.log. With -I the binary file is skipped as 'no match', and -L lists it, which here is the wrong answer to 'which files lack a TODO'. So -I and -L together need care: -I means 'pretend binary files never match', not 'ignore binary files'. Do not test the exit status of -L (Chapter 2): it is 0 when nothing is listed and 1 when something is.