3 of 8

Linux FilesystemLesson 3 of 8

Files: create, read, copy, move, remove

Create files, read them without an editor, handle many at once with globs, and avoid the ways these commands destroy data.

  • touch
  • cat
  • less
  • head
  • tail
  • wc
  • file
  • cp
  • mv
  • rm

30 min5 exercises

After this lesson you can

  • Create and fill files from the command line, and avoid overwriting by accident.
  • Read any file, or any part of a large one, with the right tool.
  • Select groups of files with globs and preview the match before acting.
  • Copy, move and remove files with the safety options switched on.

Create files

touch creates an empty file, or, if the file exists, updates its modification time:

cd ~/lab
touch notes.txt
ls -l notes.txt
-rw-rw-r-- 1 alex alex 0 Nov  3 10:12 notes.txt

To put text in a file, redirect a command’s output into it. > creates the file, or replaces it if it exists. >> appends:

echo "first" > notes.txt
echo "second" >> notes.txt
cat notes.txt
first
second

echo adds a newline. printf does not, and understands \n, so it is the better tool for multi-line text:

printf 'name=web\nport=8080\nmode=prod\n' > config.ini
cat config.ini
name=web
port=8080
mode=prod

Read files

CommandUse it to
cat filePrint a short file
cat -n filePrint with line numbers
less filePage through a long file
head -n 5 fileFirst lines
tail -n 5 fileLast lines
tail -f fileFollow a file as it grows
wc -l fileCount lines

Generate a 1000-line sample and try them:

seq -f "entry %g" 1 1000 > big.log
wc -l big.log
1000 big.log
tail -n 3 big.log
entry 998
entry 999
entry 1000

head and tail combine to cut out any range. Lines 10 to 12 are the last three of the first twelve:

head -n 12 big.log | tail -n 3
entry 10
entry 11
entry 12

| (pipe) sends the output of one command into the next. It is the main way Linux commands work together.

Page with less

less opens a file without loading it all, and lets you move around:

KeyAction
Space / bPage down / up
/text then nSearch forward, jump to next match
g / GStart / end of the file
qQuit

Follow a growing file

tail -f keeps the file open and prints new lines as they are written. It is how you watch a log live. Press Ctrl+C to stop:

tail -f big.log

Count with wc

wc counts lines, words and bytes (-l, -w, -c). Give it a redirected file and it prints only the number:

wc -l < big.log
1000

Ask a file what it is

Names lie. file reads the content:

file /etc/passwd /usr /usr/bin/ls
/etc/passwd: ASCII text
/usr:        directory
/usr/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, ...

Run file before you cat something you do not recognise. Printing a binary can garble your terminal; if that happens, type reset.

Copy, move, remove

The same three commands as for directories, with the same traps:

cp config.ini config.ini.bak
mv config.ini.bak config.ini.old
rm config.ini.old

Each has options that turn silent overwriting into a question or a refusal:

OptionEffect
-iAsk before overwriting or removing
-nNever overwrite
-vPrint what was done
-a (cp)Copy and keep permissions, owner and times

The first copy creates config.ini.bak silently. Run the same command again and it asks:

cp -i config.ini config.ini.bak
cp -i config.ini config.ini.bak
cp: overwrite 'config.ini.bak'? 

Answer n to keep the existing file.

mv -v shows the rename:

mv -v config.ini.bak config.old
renamed 'config.ini.bak' -> 'config.old'

Compare two files

diff old new prints only the difference. Here is a change to config.ini:

cp -a config.ini config.ini.bak
echo 'debug=true' >> config.ini
diff config.ini.bak config.ini
3a4
> debug=true

Read 3a4 as “after line 3 of the old file, add line 4 of the new”. > marks added lines and < removed ones. diff prints nothing when the files are identical.

Patterns: globs

Before a command runs, the shell expands patterns into matching names. The command never sees the pattern.

PatternMatches
*Any run of characters, including none
?Exactly one character
[abc], [0-9]One character from the set or range
{a,b}Not a glob: expands to each word in turn
touch a1.log a2.log b1.log c.txt notes.md
ls *.log
a1.log  a2.log  b1.log
ls ?1.log
a1.log  b1.log
ls {a,c}*
a1.log  a2.log  c.txt

A pattern with no match stays as typed, so the command gets the literal text:

echo *.zip
*.zip

Globs do not match names that start with a dot, so rm * leaves hidden files behind. They also do not cross /: * never matches a path separator.

echo *.log
a1.log a2.log b1.log

Only then:

rm *.log

Timestamps

ls -l shows the modification time. Use --time-style=long-iso for an unambiguous format, and touch -t YYYYMMDDhhmm to set one:

touch -t 202401151030 old.txt
ls -l --time-style=long-iso old.txt
-rw-rw-r-- 1 alex alex 0 2024-01-15 10:30 old.txt

Practice

0 of 5 done

Do these on your own system. They get harder. Check each result, then tick it off.

  1. Warm-up

    Create, append, count

    In ~/lab, create list.txt containing the lines one, two and three (one command). Append the line four. Then print the file with line numbers and report its line count.

    Check your result
    cat -n ~/lab/list.txt
    wc -l ~/lab/list.txt
         1	one
         2	two
         3	three
         4	four
    4 /home/alex/lab/list.txt
    Hint

    > creates or replaces. >> appends. printf understands \n.

    Solution
    printf 'one\ntwo\nthree\n' > ~/lab/list.txt
    echo four >> ~/lab/list.txt
    cat -n ~/lab/list.txt
    wc -l ~/lab/list.txt
  2. Warm-up

    Select files with a pattern

    Create the five files below. Then list, with one ls each, (1) only the .log files, (2) only the .log files whose name is a letter a or b followed by one digit.

    cd ~/lab && touch a1.log a2.log b1.log c.txt notes.md
    Check your result
    a1.log  a2.log  b1.log
    a1.log  a2.log  b1.log

    Both patterns match the same three files here. Add a10.log and run the second pattern again: it must not appear.

    Hint

    * matches any run of characters. [ab] matches one of the listed characters and [0-9] one digit.

    Solution
    ls *.log
    ls [ab][0-9].log
  3. Practice

    Back up, change, compare

    Create config.ini with three lines (name=web, port=8080, mode=prod). Make a backup that keeps the original timestamp, append debug=true to the original, and show exactly what changed with a command that prints only the difference.

    Check your result
    diff config.ini.bak config.ini
    3a4
    > debug=true
    Hint

    cp -a preserves metadata. diff old new shows what the new file added or removed.

    Solution
    cd ~/lab
    printf 'name=web\nport=8080\nmode=prod\n' > config.ini
    cp -a config.ini config.ini.bak
    echo 'debug=true' >> config.ini
    diff config.ini.bak config.ini
  4. Practice

    Ask the file what it is

    Use one command on all three paths to learn what each is: /etc/passwd, /usr and /usr/bin/ls. In one line each, say what kind of thing it is and whether you could read it with cat.

    Check your result
    /etc/passwd: ASCII text
    /usr:        directory
    /usr/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), ...

    You can cat the first. The second is a directory. The third is a compiled program and cat would print unreadable bytes.

    Hint

    The command is called file, and it looks at content, not at the name.

    Solution
    file /etc/passwd /usr /usr/bin/ls
  5. Challenge

    Triage a big file without an editor

    Generate a 1000-line log, then answer four questions about it using only the tools from this lesson: how many lines, what are the last three, what is line 5, and what are lines 10 to 12.

    seq -f "entry %g" 1 1000 > ~/lab/big.log
    Check your result

    Total 1000. Last three entry 998, entry 999, entry 1000. Line 5 is entry 5. Lines 10 to 12 are entry 10, entry 11, entry 12.

    Hint

    Line 5 is the last line of the first five. Lines 10 to 12 are the last three of the first twelve. Chain two commands with |.

    Solution
    cd ~/lab
    wc -l big.log
    tail -n 3 big.log
    head -n 5 big.log | tail -n 1
    head -n 12 big.log | tail -n 3