TT Lab
Get started
Learn Learning paths Courses

Shell Scripting

Scripts That Survive Hostile Filenames

Continue in TT Lab

Goal

You will write scripts that withstand file names that contain spaces, newlines, or start with a dash, and even options and injection strings. For each rule, you check the broken version and the fixed version side by side.

Environment

You work under /root/args. You create the materials yourself.

mkdir -p /root/args/data && cd /root/args/data
: > "my report.tmp"
: > "$(printf 'two
lines').txt"
: > "-rf"
: > "normal.txt"
ls -b

The third one is the key. You can create a file named -rf. And, as with the second one, names can also contain newlines. The only character that cannot appear in a file name is NUL.

What you will build

/root/args/split.txt    따옴표 유무에 따른 인자 수
/root/args/nul.txt      줄 단위와 NUL 단위로 센 개수
/root/args/clean.sh     파일 하나를 이름이 무엇이든 안전하게 지운다
/root/args/wrap.sh      받은 인자를 쪼개지 않고 그대로 넘긴다
/root/args/opts.sh      getopts 로 옵션을 판다
/root/args/search.sh    입력을 값으로만 쓴다
/root/args/report.md    다섯 규칙 정리

How grading works

For steps 3–7, the grader calls your scripts directly with hostile input.

wrap.sh    "a b" "c" "d  e" 를 넘겨 3개로 도착하는지
opts.sh    -v -o 값 / 붙여 쓴 -vo 값 / 값 빠진 -o / 모르는 -Z
search.sh  "alpha; touch /tmp/…" 를 넣고 그 파일이 생기는지
clean.sh   "-rf" 라는 이름을 인자로 직접 넘겨 지워지는지

Steps

  1. Create four kinds of hostile names.
  2. Count the number of arguments when quotes are added to the same value and when they are left out, and write it down.
  3. Write down the count when you count the same directory line by line and when you count it with -print0. The two numbers differ.
  4. Write clean.sh. It takes one file name as an argument and deletes it. If the file does not exist, it must end with a nonzero code.
  5. Write wrap.sh. For each argument it receives, it prints ARG:<값> on one line (the value goes in place of the placeholder).
  6. Write opts.sh. It accepts -v and -o <값> (a value) and prints the number of remaining arguments. The output has the form verbose=0 out= rest=0.
  7. Write search.sh. It takes a pattern and a file and searches. Do not use eval.
  8. Summarize the five rules and the reasons for them.

Notes

In step 4, if you extract names with find, a ./ is prepended and hides the problem. That is why the grader passes the name directly as an argument. That form is also dangerous in the field: it is what happens with for f in * or when you pass on a name given by another program as it is.

Create hostile names

Create four kinds of hostile names.

Names with a space, a newline, or starting with a dash, plus one ordinary name. If you look with ls -b, special characters appear escaped.

One quote changes the number of arguments

Count the number of arguments when quotes are added to the same value and when they are left out, and write it down.

After running set -- $f and set -- "$f", look at $#, which gives the number. Also write one line on why it turns out that way: the shell splits into words after expanding.

Counting by lines is wrong, counting by NUL is right

Write down the count when you count the same directory line by line and when you count it with -print0. The two numbers differ.

A file whose name contains a newline is counted as two when read line by line. find -print0 splits on NUL, the only character that cannot appear in a file name, so it is safe.

When a name starts with a dash

Write clean.sh. It takes one file name as an argument and deletes it. If the file does not exist, it must end with a nonzero code.

rm -f "$f" reads the name as an option if it is -rf. -- is the marker meaning "from here on, arguments". For a file that does not exist, it must end with a nonzero code, because rm -f succeeds even when the file is absent.

Pass on the received arguments as they are

Write wrap.sh. For each argument it receives, it prints ARG:<값> on one line (the value goes in place of the placeholder).

"$@" preserves each argument separately, and $* joins them into one. An unquoted $@ is also split again. The grader passes "a b" and "d e" to check that the spaces survive.

Do not build an option parser yourself

Write opts.sh. It accepts -v and -o <값> (a value) and prints the number of remaining arguments. The output has the form verbose=0 out= rest=0.

A hand-rolled parser falls apart on a joined form such as -vo 값 (the Korean word means "value"). The leading colon in getopts ":vo:" turns on quiet error mode so that you handle the : and \? branches yourself. If you forget shift $((OPTIND - 1)) at the end, the count of remaining arguments is wrong.

Input is a value, not code

Write search.sh. It takes a pattern and a file and searches. Do not use eval.

eval executes a string as shell code. The grader passes alpha; touch <파일> as the pattern (with a file path in place of the Korean word) and checks whether that file gets created. If you only block that and the normal search dies, that is also a failure.

Summarize the five rules

Summarize the five rules and the reasons for them.

For each rule, write in one line "what it prevents". In particular, if the reason for using NUL (the fact that it is the only character that cannot appear in a file name) is missing, -print0 remains just a habit.