Loops and Functions
Goal
You will learn loops and functions in a form you can actually use. In particular, you will make them not break in boundary situations such as "when no files match", "when an argument contains a space", and "when there are no arguments at all".
Why it matters
Shell functions do not return values. Values you compute go out through standard output, and success or failure is reported through the exit code. Only when you keep this convention does natural usage such as if is_number "$x" become possible. And do not parse ls for a file list; leave it to the shell's glob. $(ls *.log) breaks on file names that contain spaces, and when nothing matches it feeds an error message into the loop. Using a glob instead creates another trap, where the pattern string remains as it is when nothing matches, so you need the [ -f "$f" ] || continue line of defense.
Steps
- Create
/root/bin/loop1.shso that it prints1through5, one per line. - Create the directory
/root/loops/logsand put 4.logfiles and 2.txtfiles in it. Then create/root/bin/count.sh <디렉터리>(the argument is a directory) so that it prints only the number of.logfiles directly under that directory. If given an empty directory, it must print 0. - Put at least 3 lines into
/root/loops/users.csvin the format이름,역할(name, role), and create/root/bin/readcsv.shso that it reads that file and prints one line at a time in the format이름(역할)(name followed by the role in parentheses). Run it without arguments. - In
/root/bin/lib.sh, define aslugifyfunction. It converts the words to lowercase and joins them with hyphens./root/bin/use.shmust use it by loading that file with source.use.sh Hello World->hello-world. - Create
/root/bin/isnum.sh <문자열>(the argument is a string). If it consists only of digits, the exit code is 0; otherwise it is a nonzero code. It must print nothing. An empty string, a sign, and a decimal point are all not numbers. - Create
/root/bin/each.shso that for each argument it receives, it prints one line in the format[인자](the argument in square brackets).each.sh "a b" cmust give two lines. If there are no arguments, it prints nothing. - Create
/root/bin/sum.shso that it prints the sum of the numeric arguments it receives. If there are no arguments, print0. - Create
/root/bin/summary.sh <디렉터리>(the argument is a directory). It prints the number of.logfiles in that directory and the total number of lines in the following two lines.files=<개수>(the count)total_lines=<합계>(the total)
Notes
. /root/bin/lib.shandsource /root/bin/lib.share the same command. They do not create a new process, so the function definitions remain.wc -l < 파일(the Korean word means "file") does not attach the file name to the output, which makes it convenient for calculations.- Common mistake 1: if you give an empty directory in steps 2 and 8, the glob is not expanded and the pattern string remains as it is, so the count comes out as 1.
- Common mistake 2: if you use
for x in $*in step 6,"a b"is split into two.
Print 1 through 5
Create /root/bin/loop1.sh so that it prints 1 through 5, one per line.
You can use for x in 1 2 3 4 5 or the brace expansion {1..5}.
Count files with a glob
Create the directory /root/loops/logs and put 4 .log files and 2 .txt files in it. Then create /root/bin/count.sh <디렉터리> (the argument is a directory) so that it prints only the number of .log files directly under that directory. If given an empty directory, it must print 0.
If no files match, the pattern string remains as it is. Filter it out with [ -f "$f" ] || continue.
Read a CSV one line at a time
Put at least 3 lines into /root/loops/users.csv in the format 이름,역할 (name, role), and create /root/bin/readcsv.sh so that it reads that file and prints one line at a time in the format 이름(역할) (name followed by the role in parentheses). Run it without arguments.
Use the form while IFS=, read -r a b and redirect the input after done.
Split a function out into a library
In /root/bin/lib.sh, define a slugify function. It converts the words to lowercase and joins them with hyphens. /root/bin/use.sh must use it by loading that file with source. use.sh Hello World -> hello-world.
Define the function in lib.sh and load it in use.sh with . or source. You must not copy and paste it.
Judge with an exit code
Create /root/bin/isnum.sh <문자열> (the argument is a string). If it consists only of digits, the exit code is 0; otherwise it is a nonzero code. It must print nothing. An empty string, a sign, and a decimal point are all not numbers.
If you print the result of the judgment, you cannot use it in an if statement. Report it only with return 0 / return 1.
Safely iterate over arguments that contain spaces
Create /root/bin/each.sh so that for each argument it receives, it prints one line in the format [인자] (the argument in square brackets). each.sh "a b" c must give two lines. If there are no arguments, it prints nothing.
Check the difference between for x in "$@" and for x in $*. When there are no arguments, it must do nothing.
Sum variable arguments
Create /root/bin/sum.sh so that it prints the sum of the numeric arguments it receives. If there are no arguments, print 0.
Do not forget to initialize the accumulator variable to 0. With no arguments, 0 must come out.
Directory summary report
Create /root/bin/summary.sh <디렉터리> (the argument is a directory). It prints the number of .log files in that directory and the total number of lines in the following two lines.
files=<개수>(the count)total_lines=<합계>(the total)
Count the number of files and the total number of lines together. Even for an empty directory, 0/0 must come out.