Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Bash & the POSIX Shell Module 11

Text processing: the Unix toolbox interview questions

Interview questions on “Text processing: the Unix toolbox”: warm-ups, then what each harder question tests and how a good answer starts.

36 questions: 11 warm-ups and 25 with model answers · Levels: Fresher to senior

Warm-up questions

Short questions with short answers, each answered in full.

  1. Fresher to mid

    How does cut -f2 differ from cut -c2, and when would you use each?

    Answered in the lesson cut, tr, head, tail, wc, paste and friends

  2. Fresher to mid

    What does sort | uniq -c do, why does the sort come first, and how do you then order by count?

    sort reads the file and puts the lines in order, which makes every copy of a line sit next to its siblings. uniq then collapses adjacent duplicates and -c prefixes each run with its count, so the result is one line per unique value with a count on the left. Without sort first, uniq only drops duplicates that happen to be adjacent, and a file with interleaved rows comes out almost unchanged. To order the counts largest first, a second sort -k1,1nr -k2,2 reads field 1 as a number and reverses, with a tie-break by field 2 for a deterministic order; head -n N keeps the top N.

    From the lesson sort and uniq: counting and top-N reports

  3. Fresher to mid

    Name three options you reach for most often with grep, and say what each does.

    Three that come up every day: -i ignores case, so a search for error picks error, Error and ERROR without listing every spelling; -r reads every file under a folder and tags each match with the file's path, which turns grep into a quick "find in files"; and -q prints nothing and sets the exit status, which is why if grep -q PATTERN FILE; then …; fi is the usual test for "the file contains PATTERN". After those, -n for line numbers in a long file, -A or -B to see what came before or after a match, and -F to tell grep the pattern is plain text are the ones most scripts need. Each follows the GNU grep manual and POSIX.1-2024, so a script that uses them works on dash and bash alike.

    From the lesson grep: searching text and files

  4. Fresher to mid

    What are the two POSIX regex dialects, and which bash tools read each?

    POSIX defines two dialects, basic regular expressions (BRE) and extended regular expressions (ERE), with the same engine and different metacharacters. BRE is the default of grep and of sed; ERE is what grep -E, sed -E, awk and Bash's [[ string =~ regex ]] read. In BRE the characters + ? | ( ) { are literal and need a backslash to become metacharacters, in ERE they are metacharacters and a backslash makes them literal. POSIX regex has no \d, \s or lookaround; GNU grep accepts \w and \s as extensions, but a script that must run on BSD tools writes [[:digit:]] and [[:space:]] instead. The leftmost-longest rule applies to both dialects.

    From the lesson Regular expressions for grep, sed and awk

  5. Fresher to mid

    What does s/old/new/g do, and how is it different from s/old/new/?

    Both are the substitute command, and both read the current line into the pattern space, scan it from the left and try to replace old with new. Without the g flag, s/// stops after the first match on the line, so sed 's/cat/dog/' on 'ERROR cat sat on cat' printed 'ERROR dog sat on cat', with the second cat left alone. With g, sed keeps scanning after each replacement and changes every match on the line, so the same input became 'ERROR dog sat on dog'. A number flag N, such as s/cat/dog/2, picks only the Nth match and stops; p prints the pattern space when s/// matched (useful with -n); and i makes the match case-insensitive, which POSIX.1-2024 added to standard sed and which BSD and GNU sed both accept.

    From the lesson sed: substitute, delete and print

  6. Fresher to mid

    What are NR, NF and $NF in an awk program, and how is $NF different from NF?

    Three of awk's built-in names a learner meets first. NR is the record number across all the input awk reads, counting from 1. After awk has read the six lines of people.csv, NR is 6; after a second file, NR keeps going. NF is the number of fields in the CURRENT record, after awk has split it on FS, so NF is 3 for every row of a comma-separated people.csv read with -F,, and 1 if the same file is read with the default white-space FS. $NF is the LAST field of the current record: the field whose index is the VALUE of NF, so on Asha,34,Pune with -F, it is the string Pune. NF is a count; $NF is a value. A sister name, FNR, is NR reset at the start of each file.

    From the lesson awk: fields, patterns and actions

  7. Fresher to mid

    Why does for (k in array) come out in a different order on different awk versions, and how do you fix it?

    Answered in the lesson awk programs: arrays, joins and reports

  8. Fresher to mid

    A loop over find . -type f output breaks on a file named note with spaces.txt; what is the shortest fix?

    Pipe find's -print0 to a reader that splits on NUL instead of whitespace. The two readers that fit are xargs -0 and a Bash loop with while IFS= read -r -d '' name. The nul session's run on a tree with a tab-in-name file and a newline-in-name file showed every name arriving whole through -print0 | xargs -0, while the plain find | xargs form split the same four names into eight argument words. -print0 ends each path with a NUL byte, which no file name can hold, so the next tool has an unambiguous separator; POSIX.1-2024 added both -print0 and -0 to the standard. The alternative is find . -type f -exec cmd {} +, which fires far fewer processes than -exec cmd {} \; and hands each name over whole, but is less flexible when the command needs the path somewhere other than the end of its line.

    From the lesson find and xargs: working with many files

  9. Fresher to mid

    What does .[] do versus . in a jq filter, and when do you reach for each?

    . is the identity filter: it emits its input unchanged, so jq '.' orders.json pretty-prints the whole object. .[] is the array or object iterator: it emits each element of an array (or each value of an object) as its own value, so the next stage of a filter sees one element at a time instead of the whole collection. Reach for . when you only want jq to validate and format, or at the start of a filter where the input flows in as is. Reach for .[] whenever the next stage is "do something per item": .orders[] | select(...), .orders[] | .id, or [.orders[] | .amount] | add to sum. The two forms combine: .orders[] is read as .orders | .[], and the jq manual's "Array/Object Value Iterator" entry gives both forms.

    From the lesson jq: query and reshape JSON from the shell

  10. Fresher to mid

    Why does cut -d, give wrong results on some CSV files, and what tool handles quoted fields correctly?

    cut -d, splits each line on every comma without understanding CSV quoting. When a field contains a comma inside double quotes, like "Mehta, Ravi", cut sees two fields instead of one. The same happens with awk -F, and IFS=, read. RFC 4180 defines quoting, but these tools do not implement it. gawk --csv (gawk 5.3+) parses quoted fields correctly, keeping the comma inside its field and un-escaping doubled quotes.

    From the lesson CSV, TSV and YAML from the shell

  11. Fresher to mid

    Why store timestamps as UTC epoch seconds or an ISO 8601 Z string, and format for a local zone only at the edge?

    A UTC epoch names one instant in time; a UTC ISO 8601 string with a Z suffix says the same instant with the fields spelt out. Both are unambiguous because UTC has no daylight saving: the mapping to any local wall clock is a pure function of the tz database. A local time stored without a zone leaves the reader to guess, and even a local time stored with a zone goes through a DST-aware mapping that is not one-to-one in one hour each spring and autumn. Storing UTC avoids both problems: the field is unambiguous, every tool reads the same instant (jq, awk, GNU date, BSD date), and converting for a user is one strftime at the edge. The rule works the same for logs, queues, databases and schedulers, which is why it survives a move between machines or regions.

    From the lesson Dates, times and non-integer numbers

Questions with model answers

What each question tests, the first step of a good answer and the follow-ups to expect.

  1. Fresher to mid

    A script processes a CSV file by piping it straight to awk, but the header row appears in the output as if it were data. How would you exclude the header, and what are two different approaches?

    What it tests
    Understanding that CSV files have headers, knowing multiple ways to skip a header line, and choosing the right one for the context.
    First step
    The header is line 1. Use tail -n +2 to start from line 2, or pass NR > 1 to awk to skip the first record. The tail approach works before any processing, while awk's NR check works inside the awk program.
    Follow-ups

    From the lesson cut, tr, head, tail, wc, paste and friends

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.

    Opening the model answers with your pass…

    Model answers open with any MySmartCoPilot pass.

    • Pro pass · 1 week₹79$2.99
    • 30 days₹179$5.99
    • 1 year₹1,699$45

    One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

    It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 2 minutes
  2. Mid to senior

    A deployment script counts active servers with curl | grep UP | wc -l. The API returns a JSON array, and the script works in staging. In production, where some hosts are down, it occasionally reports one fewer server than expected. What could cause the off-by-one, and how would you fix it?

    What it tests
    Recognising that wc -l counts newlines, that text without a trailing newline loses a count, and that a pipeline relying on line counts needs to ensure each record ends with a newline.
    First step
    wc -l counts newline characters, not lines of text. If the last line of the grep output has no trailing newline, it is not counted. Verify that the API response or the grep output ends with a newline, or use grep -c UP instead of grep | wc -l.
    Follow-ups

    From the lesson cut, tr, head, tail, wc, paste and friends

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 2 minutes
  3. Fresher to mid

    A nightly job runs uniq access.log | wc -l to count unique request URLs. On a quiet Sunday the number is 480 and a hand-count agrees. On a busy weekday it says 94,000, while sort -u access.log | wc -l on the same file says 7,200. Why do the two counts disagree, and what is the one-stage fix?

    What it tests
    Knowing that uniq collapses only adjacent duplicates, that a request bursting from one client clumps the duplicates on a quiet day but not on a busy one, and the sort-first fix.
    First step
    uniq compares each line with the one before it. On a quiet day a client's repeated hits often arrive back to back, so uniq already drops most duplicates. On a busy day the requests of many clients interleave, so almost every neighbour is different and uniq keeps nearly every line, which is why the count is far too high. The fix is sort before uniq (or sort -u instead), so every copy of a URL sits beside its siblings.
    Follow-ups

    From the lesson sort and uniq: counting and top-N reports

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  4. Mid to senior

    A dashboard runs sort hits.log | uniq -c | sort -r | head -n 3 for the three busiest paths. Yesterday the top three looked right (100, 20, 2). Today the counts crossed 1000, and the dashboard printed 999, 100 and 10 on top; a path with 1500 hits never reached head. Why, and what two sort options fix it?

    What it tests
    Reading that sort without -n compares bytes, so a count that leads with '1' lands below any count that leads with '2' through '9'; the fix is -k1,1nr (numeric, reversed) with -k2,2 for a tie-break.
    First step
    sort -r reverses the order sort already picked, which is byte-wise. In byte order '1500 x' is below '999 y' because '1' is a smaller byte than '9', so the 1500 row never climbs. Change -r to -k1,1nr: the first field reads as a number and reverses, so the largest count comes first. Add -k2,2 to break ties by name so the report is deterministic.
    Follow-ups

    From the lesson sort and uniq: counting and top-N reports

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  5. Fresher to mid

    An operator runs grep ERROR app.log on a mixed-case log and gets back one line out of four they expected. On the terminal they can see the other three with their eye: 'error disk is full', 'Error reading config' and 'ERROR tar failed'. Explain what grep did, and give two ways to find all four at once.

    What it tests
    Knowing that grep is case-sensitive by default, choosing -i or an alternation such as -E '[Ee][Rr][Rr][Oo][Rr]', and keeping the search's exit status useful for a later test.
    First step
    Plain grep ERROR app.log matches E, R, R, O, R exactly, so only the all-caps line qualifies. The three others hold the same letters in different case and are dropped. Add -i so grep ignores case, or write the pattern as grep -E '[Ee][Rr][Rr][Oo][Rr]' when a script must stay portable to a grep that lacks -i.
    Follow-ups

    From the lesson grep: searching text and files

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  6. Mid to senior

    A developer runs grep -r 'addEventListener' . in a Node.js project and the search runs for 40 seconds, printing thousands of lines, most of them inside node_modules and dist. Explain where the time went, and show the one-line search that finds the three matches in src/ in under a second.

    What it tests
    Knowing that -r walks every file under the folder, that --exclude-dir prunes a whole branch of the walk, that --include narrows by name, and ordering the two for the clean result.
    First step
    -r reads every file in the tree. node_modules alone can hold tens of thousands of files, many of them bundled JavaScript that includes the pattern. Prune the two heavy folders and keep only source files: grep -rn --exclude-dir='node_modules' --exclude-dir='dist' --include='.js' --include='.jsx' 'addEventListener' src.
    Follow-ups

    From the lesson grep: searching text and files

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  7. Mid to senior

    A clean-up script runs grep -c '^[a-z]' members.csv to count lines that start with a lowercase name. On a developer's Mac the count is 1,842, on the Linux build server it is 2,310 for the same file. Explain the gap, and fix the check so it gives the same answer wherever it runs.

    What it tests
    Knowing that [a-z] walks the collation order of the current locale, that two computers often disagree about what that order is, and switching to a POSIX character class or pinning LC_ALL.
    First step
    The range [a-z] is read in collation order: on a Mac with en_IN.UTF-8 the letters sort A, a, B, b, ..., so [a-z] covers most lowercase letters and most uppercase except Z. On the Linux server with LC_ALL=C.UTF-8, letters sort A, B, ..., a, b, ..., so [a-z] is only the 26 lowercase. Either write [[:lower:]], which is lowercase in every locale, or set LC_ALL=C before the grep.
    Follow-ups

    From the lesson Regular expressions for grep, sed and awk

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  8. Fresher to mid

    check.sh under Bash 5.3 has if [[ $line =~ "^[0-9]+$" ]]; then echo integer; fi. The author who wrote it swears it worked yesterday, but today every input is reported as not an integer. Explain what the quotes did, and give the form a Bash script should use for an [[ =~ ]] regex.

    What it tests
    Knowing that Bash 3.2 and later treat a quoted part of the regex as a literal string, how to see the trap without a long debugging session, and the safe form that keeps the regex a regex.
    First step
    Bash 3.2 changed the quoting rule of [[ =~ ]]: every quoted character is matched literally. "^[0-9]+$" becomes the eight-character string ^ [ 0 - 9 ] + $, so the match fails for any input that is not that exact string. Put the regex in a variable, with no quotes on the use: re='^[0-9]+$'; [[ $line =~ $re ]].
    Follow-ups

    From the lesson Regular expressions for grep, sed and awk

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  9. Mid to senior

    A deploy script runs sed -i 's/host=localhost/host=app.example.com/' config.yaml on a Linux CI job, where it works, and on a developer's macOS laptop, where it fails with "No such file or directory: files/host=localhost/host=app.example.com/". Explain each symptom, and fix the script so one line works on both.

    What it tests
    Knowing that -i's argument shape differs between GNU and BSD sed, that macOS reads the script as the backup suffix, and choosing the portable rename pattern over an OS-specific branch.
    First step
    Read the macOS line as BSD sed does: -i takes a backup suffix next, so it took s/host=localhost/host=app.example.com/ as the suffix and expected the next word to be the script; the file it tried to open was config.yaml with that suffix appended, which does not exist. Rewrite the line as sed 's/host=localhost/host=app.example.com/' config.yaml > config.yaml.tmp && mv config.yaml.tmp config.yaml and add a trap EXIT that removes config.yaml.tmp on failure.
    Follow-ups

    From the lesson sed: substitute, delete and print

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  10. Fresher to mid

    A rewrite step runs sed 's/\/user\/\([0-9]*\)\/profile/\/u\/\1/' routes.txt to shorten /user/42/profile to /u/42. The output has /u/ where the id should be, every id is lost, and the file is only partly rewritten. Explain each symptom, and rewrite the one line as a sed script a teammate can read.

    What it tests
    Choosing a delimiter that does not collide with the pattern, knowing that BRE needs \( \) for groups, and seeing that -E lets ( ) group without backslashes so the id is captured and written back.
    First step
    The pattern used / as the delimiter and / inside the path, which forced a backslash before every path /; the escapes are correct but hard to read, and a typo there is why the id was lost (the group probably ended one character too early). Switch the delimiter to | and open ERE so the groups need no backslashes: sed -E 's|/user/([0-9]+)/profile|/u/\1|' routes.txt. The id is now captured by \1 and written back cleanly.
    Follow-ups

    From the lesson sed: substitute, delete and print

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  11. Mid to senior

    A shell script joins continuation lines (those ending with a backslash) to the next line with a sed script that uses N and a branch loop. The output doubles every joined line: "line1 continued" appears twice. What could cause that, and how do you fix it?

    What it tests
    Understanding that N appends without clearing the pattern space, that sed prints the pattern space at the end of each cycle unless -n is set, and that a branch loop must eventually print and start fresh.
    First step
    Trace what the script does on a two-line continuation: read line 1, match the backslash, N appends line 2, the substitution removes the backslash and newline, the branch jumps back, but if the script does not delete or print before the next iteration, the joined line is still in the pattern space when the cycle ends. The cycle then prints it, and the next input line appends to it again.
    Follow-ups

    From the lesson sed beyond s///: hold space and multiline

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  12. Fresher to mid

    A sed one-liner meant to reverse the lines of a file dropped the last line from the output. The input had three lines, the output had two. The script was sed '1!G;h;d'. What happened, and what small change fixes it?

    What it tests
    Knowing that d deletes the pattern space and starts the next cycle, that $ addresses the last line, and that $!d deletes on every line except the last, leaving the last line to print.
    First step
    Walk through the script on a three-line file. On line 1: 1!G skips G, h copies line 1 to the hold space, d deletes and starts the next cycle. On line 2: G appends line 1, h saves line2\nline1, d deletes. On line 3: G appends, h saves line3\nline2\nline1, d deletes. No line ever prints because every cycle ends with d.
    Follow-ups

    From the lesson sed beyond s///: hold space and multiline

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  13. Fresher to mid

    A script runs awk -F, '$3 == "Pune" { total += $2 } END { print total }' invoices.csv. On the author's Mac it prints 72500 (matches a hand count). On the Linux CI runner, same file and the same gawk 5.4.1, it prints 0. Explain the zero and give a portable fix.

    What it tests
    Noticing that the file is edited on Windows and carries CRLF line endings on the CI runner, so $3 is "Pune\\r" (five bytes), the string compare fails on every record, and total stays 0; naming a one-line fix.
    First step
    Open the file in a byte-aware viewer such as od -c or cat -v; a trailing \\r on every line is the clue. Because awk's default record separator is \\n, a CRLF file leaves \\r at the end of the last field of every record, so $NF (and $3 on this file) is "Pune\\r", not "Pune". The equality test fails on every row, no amounts are added, and the END block prints 0.
    Follow-ups

    From the lesson awk: fields, patterns and actions

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  14. Mid to senior

    A dashboard runs awk -F, '{ print $2 }' hits.csv | sort | head to list the biggest counts. This week the top is 999, 100, 10; a row of 1500 never reaches head. Inside awk, the filter $2 < "100" also lets 1000 and 1500 through. Explain both symptoms and give the fix each needs.

    What it tests
    Reading that sort without -n is a byte-wise compare so 1 is below 2 through 9, that an awk comparison with a string constant on one side is byte-wise too, and the two fixes: sort -n and a numeric constant.
    First step
    sort without -n reads the start of each line as bytes: "1500" sorts below "999" because the first byte 1 is below 9. Switch to sort -nr for a numeric sort. Inside awk, "100" on the right of a compare is a STRING constant, which forces a byte compare: "1500" < "100" is true. Drop the quotes so the compare reads $2 < 100, and awk uses numbers.
    Follow-ups

    From the lesson awk: fields, patterns and actions

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  15. Mid to senior

    A billing script joins orders to customers with awk 'FNR==NR { c[$1]=$2; next } { print $0, c[$2] }' customers orders. One day a customer complains that their invoice shows no name. What went wrong, and how would you make the script report the problem instead of hiding it?

    What it tests
    Knowing that a missing key returns empty, not an error, and adding a guard or a stderr message.
    First step
    The customer id in the order file was not in the customer file. awk's c[$2] returned the empty string, so the invoice printed a blank. Add ($2 in c) as a pattern to skip rows with no match, or an if block that prints missing ids to stderr so the billing team can fix the data.
    Follow-ups

    From the lesson awk programs: arrays, joins and reports

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  16. Mid to senior

    An awk script writes intermediate results to a temp file in the main loop, then reads them back with getline in END to produce a summary. The summary comes out empty on a small input and only partly filled on a large one. What is the likely cause, and what one-line fix makes it reliable?

    What it tests
    Recognising that writes are buffered, that nothing reaches disk until the buffer flushes or close() runs, and that close both flushes the buffer and resets the read position to the start.
    First step
    The write buffer was not flushed before the getline. A small input sits entirely in the buffer, so getline finds an empty file; a large input flushes in parts, so getline sees only some lines. Call close("tempfile") before the first getline: it flushes the buffer and resets the read position to the start, so getline reads every line.
    Follow-ups

    From the lesson awk programs: arrays, joins and reports

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  17. Mid to senior

    A nightly cleanup runs find /var/data -name '*.log' -o -mtime +7 -delete to drop logs older than a week. A week later every old log is still there, and the uploads and backups older than a week are gone. Explain both symptoms, and give the fix and the dry run that catch it on GNU and BSD find.

    What it tests
    Reading find's precedence (-a binds tighter than -o), knowing that an action such as -delete turns off the implicit -print, and the habit of a -print dry run with the same grouping before -delete.
    First step
    find reads the expression as -name '.log' -o ( -mtime +7 -a -delete ). A .log file makes the left side true, so the -o stops there and nothing deletes it; any other path eight or more days old reaches -delete. State the intent as one AND chain: find /var/data -type f -name '.log' -mtime +7 -print as the dry run, read the list, then put -delete where -print is.
    Follow-ups

    From the lesson find and xargs: working with many files

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  18. Fresher to mid

    A cron job runs find /tmp -name '*.sock' -mtime +1 | xargs rm. On a quiet day the walk is empty: on Linux cron mails "rm: missing operand", on macOS the job stays silent. On another day a socket named "my app.sock" qualifies. Explain both days and rewrite the line safely.

    What it tests
    GNU xargs runs the command once on empty input and BSD xargs does not; -r (POSIX.1-2024) skips that run; -print0 | xargs -0 keeps spaces and newlines in names from splitting the arguments.
    First step
    GNU xargs with no input still starts the command once with no arguments; BSD xargs does not. The "missing operand" line is rm complaining about that empty run. On the other day xargs splits "my app.sock" into two words, so rm looks for "/tmp/my" and "app.sock". Add -r and -print0 / -0: find /tmp -name '*.sock' -mtime +1 -print0 | xargs -0 -r rm.
    Follow-ups

    From the lesson find and xargs: working with many files

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  19. Mid to senior

    A nightly job runs jq ".orders[] | select(.city == \"$CITY\")" orders.json with $CITY read from a config. The usage report counted all 4,218 orders in the file; a hand-check says only 1,100 are in that city. Explain what the filter actually ran, and give the one-line fix that keeps the pipeline correct for any value of CITY.

    What it tests
    Reading that a shell-interpolated filter is a two-stage parse, that a value with " or or reshapes the jq program silently, and knowing the --arg form.
    First step
    Read what the shell handed to jq. If $CITY was the plain word Pune, jq saw .orders[] | select(.city == "Pune"). If it was the string Pune" or "Chennai, jq saw .orders[] | select(.city == "Pune" or "Chennai"), parsed as (.city == "Pune") or "Chennai"; the non-empty string is truthy, so every order matches. Rewrite as jq --arg city "$CITY" '.orders[] | select(.city == $city)' orders.json: the program is single-quoted and --arg passes the value as a jq string.
    Follow-ups

    From the lesson jq: query and reshape JSON from the shell

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  20. Mid to senior

    A daily totals report runs jq -r '.orders | group_by(.city) | map("\(.[0].city) \((map(.amount) | add))") | .[]' expecting three lines, one per city. When Chennai has no orders the report drops to two; when a Mumbai order appears it has four. Rework so Chennai prints a zero and Mumbai still prints.

    What it tests
    Knowing group_by only sees values present in the input (no zero rows); passing a fixed city list with --argjson and pinning every city to a total so missing cities print 0 and extra cities still print.
    First step
    group_by(.city) builds one group per distinct .city value in the input. No Chennai in the data means no Chennai group; a Mumbai row makes a Mumbai group. Pin the output list: jq --argjson cities '["Chennai","Kochi","Pune"]' -r '(.orders | group_by(.city) | map({(.[0].city): (map(.amount)|add)}) | add) as $t | $cities | map("\(.) \($t[.] // 0)") | .[]' orders.json — every city in $cities prints, with 0 when absent.
    Follow-ups

    From the lesson jq: query and reshape JSON from the shell

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  21. Fresher to mid

    An API-check runs if ! jq -e '.enabled' response.json; then echo "feature off"; exit 1; fi. On {"enabled": false} it prints feature off (correct). On a payload with .enabled missing it does the same, but the product owner needs "missing" (a rollout bug) kept apart from "set to false". Explain what jq -e saw and give a one-line fix.

    What it tests
    Reading the jq manual's --exit-status rule (exit 1 on false or null, exit 4 on no output); using .enabled == null or has("enabled") to tell "missing" apart from "set to false".
    First step
    The jq manual says -e returns exit 1 on false or null and exit 4 on no output. On {"enabled": false} the filter returned false, exit 1. On the missing-field payload the filter returned null (jq reads a missing key as null), also exit 1 — the two cases match under -e. Switch to the real question: jq -e 'has("enabled")' response.json >/dev/null, true for {"enabled": false} and false when the key is missing.
    Follow-ups

    From the lesson jq: query and reshape JSON from the shell

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  22. Mid to senior

    A bash script sums the third column of a CSV. One row has a name with a comma, and the total is too high by exactly that row's amount. How do you diagnose and fix the script?

    What it tests
    Identify the quoted-comma row, explain the field shift, switch to gawk --csv.
    First step
    Run awk -F, '{ print NF }' file | sort | uniq -c to see the field count per row. A row with a quoted comma shows one extra field, confirming that simple splitting broke the data.
    Follow-ups

    From the lesson CSV, TSV and YAML from the shell

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  23. Mid to senior

    A YAML config uses anchors for shared defaults. A team member used sed to change a timeout value, and now two services have different timeouts though they should share the same default. What went wrong?

    What it tests
    Explain anchor expansion, show that sed edits text not structure, fix with yq.
    First step
    Open the YAML and find the anchor (&defaults) and its references (*defaults). sed changed the literal text in one place but did not know about the anchor relationship, so the other references still expand to the old value or the edit broke the anchor entirely.
    Follow-ups

    From the lesson CSV, TSV and YAML from the shell

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  24. Mid to senior

    A crontab entry runs a billing summary at 02:30, and the server's zone is America/New_York. Before the spring and autumn clock changes, ops ask whether the summary can be skipped or run twice. What does cronie do on each day, which local times are at risk, and what change removes the question?

    What it tests
    Knowing which local hour is missing in spring and which repeats in autumn, how cronie handles clock changes of under three hours, and moving the schedule to UTC with the entry's time converted.
    First step
    Map both days. In spring the clock jumps from 02:00 to 03:00, so 02:30 does not exist; cronie's manual says jobs from a skipped interval run right after the change. In autumn the hour from 01:00 to 02:00 repeats; 02:30 comes once, and cronie avoids running a fixed-time job twice. To remove the question, set CRON_TZ=UTC and write the time in UTC.
    Follow-ups

    From the lesson Dates, times and non-integer numbers

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  25. Fresher to mid

    A nightly invoice job sums 20,000 subscription lines, each 24.99 rupees, with awk 'BEGIN { for (i=0; i<20000; i++) t += 24.99 } END { printf "%.2f", t }' and prints 499,799.99. The product owner expects 499,800.00. What happened, and what one change in the pipeline fixes it?

    What it tests
    Reading that 24.99 is not exact as an IEEE 754 double, that 20,000 additions accumulate the small error, and that the fix is to count in integer paise (minor units) and format at the end.
    First step
    24.99 is not exactly representable in a double; the closest value is a shade below 24.99. Twenty thousand sums accumulate the shortfall into about 0.01 rupees of error, which the %.2f step rounds to 499,799.99. The one change is to count in paise: amounts become 2499 each; the awk accumulator is an integer t = 2499 * 20000 = 49980000; only the final line divides by 100 and formats, printing 499,800.00 cleanly.
    Follow-ups

    From the lesson Dates, times and non-integer numbers

    Model answer, Pro.

    In the answer:

    • Model answer
    • Follow-up answers
    • Common mistakes
    • Rubric
    • Variants
    • about 5 minutes

All Bash & the POSIX Shell interview questions · The Bash & the POSIX Shell track

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.