File Handling in Bash for DevOps
In the previous article we used loops to process multiple servers, many log lines, and retry commands that may fail temporarily. The next natural step is file handling: reading logs, writing reports, appending output, creating configuration files, finding old files, and passing file lists to other commands.
In DevOps, most small automation tasks touch files in one way or another: service logs, .env files, Nginx configs, YAML manifests, host lists, build artifacts, or backups. This article focuses on practical Bash patterns: cat, head, tail, redirection, here-doc, tee, line-by-line reading, file checks, find, xargs, and two hands-on examples.
Quickly reading file content with cat, tac, head, and tail
cat reads one or more files and writes the content to standard output. According to GNU Coreutils, if no file is provided, cat reads from standard input.
| |
A few commands you will often use when inspecting logs or config files:
| |
tail -f follows the current file descriptor. When a log is rotated, the old file may be renamed and the application may open a new file. GNU tail -F is equivalent to --follow=name --retry, which is often more useful in operations because it keeps trying to reopen the file by name.
Redirecting output: overwrite, append, and split stdout/stderr
Bash processes redirections from left to right. Common forms include:
| |
Example: write logs for a deploy step:
| |
Using a block like { ...; } >> "${LOG_FILE}" 2>&1 lets you collect logs from many commands into the same file without repeating the redirection on every line.
Note: > overwrites the file. If you want to reduce the risk of accidental overwrites in the current shell, you can enable set -o noclobber; when you intentionally need to overwrite, use >| file.
Here-doc: create configuration files from a script
A here-doc (<<EOF) sends multiple lines of text to a command’s stdin. It is very useful for creating sample configs, unit files, or small JSON payloads.
| |
If the delimiter is not quoted, Bash expands variables inside the here-doc:
| |
If you want to keep $, backticks, or ${VAR} literally in the output file, quote the delimiter:
| |
According to the Bash Manual, when using <<-EOF, Bash strips leading tabs from the here-doc. This can make a script easier to read, but it strips tabs only, not spaces.
tee: show output and save it at the same time
Redirection with > sends output to a file and usually no longer shows it in the terminal. tee copies standard input to standard output and writes it to a file at the same time.
| |
According to GNU Coreutils, tee overwrites the file unless you use -a; tee -a appends to the file.
Example: watch build logs while saving an artifact log:
| |
Here, 2>&1 is placed before the pipe so stderr also flows through tee. If you write only ./build.sh | tee ..., many errors printed to stderr will still appear in the terminal but will not be saved to the log file.
Reading a file line by line
A safe pattern for line-by-line reading is while IFS= read -r LINE; do ...; done < file.
| |
Quick explanation:
IFS=preserves leading and trailing whitespace.read -rprevents\from being treated as an escape character.done < "${SERVER_FILE}"avoids thecat file | while ...pattern, which may run the loop in a subshell in some shells and lose variables after the loop.- Quote
"${SERVER_FILE}"so paths containing spaces still work.
Check files, directories, and permissions before operating
Before reading, writing, or deleting files in automation, check the conditions explicitly. Some useful tests inside [[ ... ]]:
| |
Example: back up a config only when the file exists and is readable:
| |
The -- after cp ends the option list. This is a good habit when a variable may start with -.
find: search for files by condition
find is useful when you need to search by name, type, time, size, or nested directories.
| |
Common predicates:
-type f: regular files only.-type d: directories only.-name "*.log": match by file name.-mtime +7: files whose modification time is more than 7 days ago.-size +100M: files larger than 100 MiB, using GNU find syntax.-maxdepth 1: do not descend too far from the current directory.
Example: review logs older than 14 days without deleting them yet:
| |
When writing a cleanup script, run with -print first to review the list, then change it to a delete or archive action.
xargs: pass file lists to another command
xargs reads data from stdin, groups it into arguments, and runs a command. It is useful when the file list is long or when you need to pass find results to another command.
Avoid the default form for arbitrary file names:
| |
By default, xargs splits input on whitespace, so file names containing spaces, tabs, or newlines may be interpreted incorrectly. GNU findutils recommends using find -print0 together with xargs -0 to separate entries with the NUL character:
| |
If you use GNU xargs, add -r so the command is not run when input is empty:
| |
Another option is find -exec ... {} +, which is portable and avoids the pipe:
| |
DevOps practice: manual log rotation
In production, log rotation should usually be handled by logrotate or a logging stack. But understanding a small rotate script helps you grasp file operations clearly: check size, rename, create a new file, compress, and clean up.
| |
A few notes:
: > "${LOG_FILE}"creates a new empty file using the shell builtin:and an empty output redirection.- This script is suitable for learning or for a small service. With an app that writes logs continuously, manual rotation may require a signal or log reopen behavior depending on the application.
- Use
find ... -print -deleteso you can see which files are removed while cleaning up.
DevOps practice: back up config before deployment
Before deployment, a safe step is backing up important config files into a timestamped directory.
| |
cp -p preserves mode, ownership, and timestamps when system permissions allow it. TARGET="${BACKUP_DIR}${CONFIG_FILE}" keeps the original path structure inside the backup directory, which makes restore easier.
Common mistakes
- Using
>when you meant append:>overwrites the file; use>>ortee -aif you want to append. - Putting redirections in the wrong order:
command 2>&1 >fileis different fromcommand >file 2>&1. Bash processes redirections from left to right. - Forgetting to quote paths: Always use
"${FILE}", especially with paths read from input. - Using default
xargswith arbitrary file names: Preferfind -print0 | xargs -0orfind -exec ... {} +. - Deleting files before reviewing: For cleanup, run
find ... -printfirst, then add-deleteorrm. - Unexpected here-doc expansion: Quote the delimiter (
<<'EOF') if you want to keep${VAR}literally in the output file.
Implementation notes
- When applying this to your own project, clearly separate the operation type:
- Quick file/log viewing →
cat,head,tail,tail -F. - Script logging → redirection block or
tee -a. - Multi-line config creation → here-doc, with a quoted delimiter when you need literal templates.
- Searching/processing many files →
find,find -exec ... {} +, orfind -print0 | xargs -0.
- Quick file/log viewing →
- Best practices:
- Start scripts with
set -euo pipefailwhen appropriate. - Check
-r,-w,-f, and-dbefore dangerous operations. - Use
--before path variables in commands such ascp,mv,rm, andgzip. - For cleanup, log affected files with
-printorecho.
- Start scripts with
- Troubleshooting:
- Log file does not capture stderr? → Make sure
2>&1appears before the pipe or that redirection order is correct. tail -fdoes not show new logs after rotation? → Trytail -F.- Script breaks with file names containing spaces? → Check variable quoting and how
xargsis used.
- Log file does not capture stderr? → Make sure
🎯 Final thoughts
File handling is a core skill when writing Bash for DevOps. Once you understand redirection, here-doc, tee, line-by-line reading, permission checks, find, and xargs, you can write small but safer scripts for logs, configs, backups, and cleanup.
In the next article, we will move into text processing with grep, awk, and sed: filtering logs, extracting columns, changing config, and counting data from text files more effectively. 🚀
References
- GNU Bash Manual — Redirections — Official documentation for redirection, file descriptors, and here-docs.
- GNU Coreutils Manual — cat — How
catreads and writes files/stdin/stdout. - GNU Coreutils Manual — head — Official documentation for
head -nandhead -c. - GNU Coreutils Manual — tail — Official documentation for
tail -f,tail -F, and--follow=name. - GNU Coreutils Manual — tee — Official documentation for
teeandtee -a. - GNU Findutils Manual — Official documentation for
find,xargs,-print0, andxargs -0.
