The script that refuses to run
You typed a few lines of commands into a text file, saved it, and tried to run it. The terminal spat back Permission denied. Or worse, you typed the filename and got command not found. You know the commands work individually. You just want them to run together. This is the first hurdle of scripting on Fedora.
The error is not a bug. It is the operating system doing exactly what it was designed to do. Linux treats every file as data until you tell it otherwise. The kernel does not care about file extensions. A file named script.sh is just bytes on disk. When you type a command, the shell checks two things. First, is the file marked executable? Second, does the file tell the kernel which program should interpret it? If either check fails, execution stops.
The shebang line bridges the gap between a text file and a program. Permissions enforce the rule that files cannot run unless you explicitly allow it. SELinux adds a third layer that checks whether the file is allowed to execute in its current location. Understanding these three mechanisms turns a confusing error into a predictable workflow.
Check the exit code. A silent failure is worse than an error message.
How the kernel executes a script
When you run ./script.sh, the shell passes the filename to the kernel. The kernel reads the first 128 bytes of the file. If the file starts with #!, the kernel treats the rest of that first line as the path to an interpreter. It then re-executes the file, passing the script path as an argument to that interpreter.
If the file lacks a shebang, the kernel assumes it is a binary executable. It tries to load it as machine code. That fails with Exec format error. If you run the script by typing bash script.sh, you bypass the shebang entirely. The shell reads the file directly. This works, but it hides the shebang requirement and makes the script dependent on how you invoke it.
Permissions control who can trigger this process. The executable bit (x) on a file tells the kernel that the file may be executed. Without it, the kernel refuses to hand the file to an interpreter, even if the shebang is correct. This prevents accidental execution of data files.
SELinux adds a context label to every file. A script in your home directory has a context like user_home_t. A script in /usr/local/bin has a context like bin_t. If you move a script to a system directory without updating its label, SELinux blocks execution. The error looks like a permission failure, but the root cause is the security context.
Run bash -x script.sh before you guess. The trace output shows exactly where execution stops.
Writing the script
Create the file in your home directory. Use an editor you are comfortable with. nano is simple and stays in the terminal. vim is powerful and requires learning keybindings. The editor choice does not affect how the script runs.
Here is how to create a script that demonstrates variable assignment, user input, conditional logic, and system information retrieval.
# Create a new file in the home directory.
# nano opens the file in the terminal editor.
nano ~/hello_fedora.sh
Paste the following content into the editor. Every line serves a specific purpose. Read the comments to understand the mechanics.
#!/bin/bash
# Shebang tells the kernel to use bash as the interpreter.
# This path works on all Fedora installations.
# Enable strict error handling.
# -e exits on any command failure.
# -u treats unset variables as errors.
# -o pipefail catches failures in pipelines.
set -euo pipefail
# Define a greeting variable.
# No spaces around the equals sign.
GREETING="Welcome to Fedora Linux"
# Get the current username.
# Command substitution captures the output of whoami.
USER=$(whoami)
# Check if the user is root.
# Double brackets handle empty variables safely.
# Quotes around variables prevent word splitting.
if [[ "$USER" == "root" ]]; then
echo "$GREETING, Administrator."
echo "Running as root is generally discouraged for daily tasks."
else
echo "$GREETING, $USER."
echo "You are running as a standard user."
fi
# Display the current Fedora release version.
# /etc/redhat-release exists on all Fedora and RHEL systems.
echo "Current OS: $(cat /etc/redhat-release)"
# Exit with status 0 to indicate success.
exit 0
Save the file and exit the editor. The script is now a text file. It cannot run yet. The kernel sees it as data. You must change the permissions to mark it executable.
Use set -euo pipefail in every script. Uncaught errors hide bugs until production.
Making the script executable
The chmod command modifies file permissions. The +x flag adds the executable bit for the owner, group, and others. This tells the kernel that the file is allowed to be executed.
Here is how to apply the executable permission and verify the change.
# Add executable permission to the script.
# The +x flag modifies the mode bits on the inode.
chmod +x ~/hello_fedora.sh
# Verify the permissions changed.
# The output should show -rwxr-xr-x or similar.
# The x characters indicate executable status.
ls -l ~/hello_fedora.sh
Run the script using ./ to specify the current directory. The shell does not search the current directory by default for security reasons. If you omit ./, the shell searches the directories listed in your $PATH environment variable. Your home directory is not in $PATH.
Here is how to run the script and check the result.
# Run the script from the current directory.
# The ./ prefix tells the shell to look in the current path.
./hello_fedora.sh
# Check the exit status of the last command.
# 0 means success. Any other number means failure.
echo $?
If the script runs correctly, you will see the greeting, the user status message, and the Fedora release version. The exit code will be 0. If you see Permission denied, verify the executable bit with ls -l. If you see command not found, verify you typed ./ before the filename.
Check the exit code. A silent failure is worse than an error message.
Common pitfalls and error patterns
Scripts fail for predictable reasons. Recognizing the error pattern saves debugging time.
Missing shebang or wrong interpreter
If you forget the shebang, the kernel tries to execute the file as a binary. You get bash: ./script.sh: /bin/bash^M: bad interpreter: No such file or directory if you have Windows line endings, or Exec format error if the file is pure text. If you use #!/bin/sh instead of #!/bin/bash, you might lose bash-specific features like arrays or [[ ]] conditionals. Fedora uses bash as the default login shell. Stick to #!/bin/bash for scripts that use bash features.
Unquoted variables
If you write if [ $USER == root ] and $USER is empty, the command becomes if [ == root ]. This causes a syntax error. Always quote variables in test conditions. Use if [[ "$USER" == "root" ]] or if [ "${USER:-}" == "root" ]. The :- syntax provides a default empty string if the variable is unset.
SELinux context blocks
If you move a script to /usr/local/bin and run it, you might get Permission denied even with chmod +x. SELinux blocks execution because the file has a user_home_t context instead of bin_t. The file permissions are correct. The security label is wrong.
Here is how to fix the SELinux context on a moved script.
# Restore the default SELinux context for the file.
# restorecon reads the file location and applies the correct label.
# The -v flag shows the change in verbose mode.
sudo restorecon -v /usr/local/bin/hello_fedora.sh
# Verify the context changed to bin_t.
# The output should show system_u:object_r:bin_t:s0.
ls -Z /usr/local/bin/hello_fedora.sh
Never use setenforce 0 to fix script execution errors. Disabling SELinux hides the real problem and exposes the system to security risks. Fix the label instead.
Relabel the context before you move the script. SELinux blocks execution on wrong labels, not wrong permissions.
Debugging scripts
When a script fails, do not guess. Use bash's built-in debugging tools. The -x flag enables trace mode. Bash prints every command before it executes, with variable values expanded. This shows exactly where the script stops.
Here is how to run a script in debug mode.
# Run the script with trace output enabled.
# The -x flag prints each command before execution.
# This reveals variable values and control flow.
bash -x ./hello_fedora.sh
The output will show lines starting with +. These are the commands bash is executing. If the script fails, the last + line shows the failing command. Check the variable values on that line. Verify the syntax. Fix the error.
Run bash -x script.sh before you guess. The trace output shows exactly where execution stops.
When to use bash vs alternatives
Choose the right tool for the job. Bash is not always the best choice.
Use Bash when you are chaining existing command-line tools together. Use Python when you need complex data structures or math. Use systemd timers when the script needs to run on a schedule or at boot. Use a cron job when you need legacy compatibility. Stay on Bash if the task is simple and the tools are already installed.
Use Bash when you are chaining existing command-line tools together. Use Python when you need complex data structures or math. Use systemd timers when the script needs to run on a schedule or at boot. Use a cron job when you need legacy compatibility. Stay on Bash if the task is simple and the tools are already installed.