Sometimes 'head -c 0' or 'head -n 0' is used by scripts to test if a
file or directory can be opened for reading. This test makes sure we
don't try to exit immediately before opening files and that we don't
stat directory arguments. E.g., the following command should not fail:
$ head -c 0 .
Note that tail does not behave this way after Pádraig's commit from 2013:
commit 7abf99e1907b1b05cb45eacaa98bfa73efe0ab92
Author: Pádraig Brady <[email protected]>
AuthorDate: Tue Mar 26 00:36:01 2013 +0000
Commit: Pádraig Brady <[email protected]>
CommitDate: Thu Apr 4 03:01:48 2013 +0100
tail: exit without reading input if would never output
* src/tail.c (main): If -n0 or -c0 were specified without -f,
then no data would ever be output, so exit without reading input.
* tests/tail-2/tail-n0f.sh: Augment the related test with this case.
I thought this was incorrect and was going to suggest changing it.
However, after some thinking I think I agree with the behavior. Since
'tail' starts at the end of the file it is better to quit early
instead of seeking, or reading if the input file is not seekable, when
we know there will be no output.
-- 8< --
* tests/head/head-n0.sh: New file.
* tests/local.mk: Add the new test case.
---
tests/head/head-n0.sh | 81 +++++++++++++++++++++++++++++++++++++++++++
tests/local.mk | 1 +
2 files changed, 82 insertions(+)
create mode 100755 tests/head/head-n0.sh
diff --git a/tests/head/head-n0.sh b/tests/head/head-n0.sh
new file mode 100755
index 000000000..8da4597e7
--- /dev/null
+++ b/tests/head/head-n0.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+# Make sure that 'head -n 0' and 'head -c 0' opens files for reading.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ head
+getlimits_
+
+mkdir dir || framework_failure_
+echo a > file || framework_failure_
+
+# Test 'head -n 0' with an existing file or directory.
+for args in dir file; do
+ for opt in -n -c; do
+ head $opt 0 $args >out 2>err || fail=1
+ compare /dev/null out || fail=1
+ compare /dev/null err || fail=1
+ done
+done
+
+# Test 'head -n 0' with multiple existing arguments and headers disabled.
+for args in 'dir file' 'file dir'; do
+ for opt in -n -c; do
+ head -q $opt 0 $args >out 2>err || fail=1
+ compare /dev/null out || fail=1
+ compare /dev/null err || fail=1
+ done
+done
+
+# Test 'head -n 0' with multiple existing arguments and headers enabled.
+for args in 'dir file' 'file dir'; do
+ file1=$(echo "$args" | cut -d ' ' -f1)
+ file2=$(echo "$args" | cut -d ' ' -f2)
+ cat <<EOF > exp || framework_failure_
+==> $file1 <==
+
+==> $file2 <==
+EOF
+ for opt in -n -c; do
+ head $opt 0 $args >out 2>err || fail=1
+ compare exp out || fail=1
+ compare /dev/null err || fail=1
+ done
+done
+
+# Test 'head -n 0' with a missing file.
+cat <<EOF >exp || framework_failure_
+head: cannot open 'missing1' for reading: $ENOENT
+EOF
+for opt in -n -c; do
+ returns_ 1 head $opt 0 missing1 >out 2>err || fail=1
+ compare /dev/null out || fail=1
+ compare exp err || fail=1
+done
+
+# Test 'head -n 0' with multiple missing files.
+cat <<EOF >exp || framework_failure_
+head: cannot open 'missing1' for reading: $ENOENT
+head: cannot open 'missing2' for reading: $ENOENT
+EOF
+for opt in -n -c; do
+ returns_ 1 head $opt 0 missing1 missing2 >out 2>err || fail=1
+ compare /dev/null out || fail=1
+ compare exp err || fail=1
+done
+
+Exit $fail
diff --git a/tests/local.mk b/tests/local.mk
index d72361e40..727cf8b58 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -365,6 +365,7 @@ all_tests = \
tests/groups/groups-process-all.sh \
tests/groups/groups-version.sh \
tests/head/head-c.sh \
+ tests/head/head-n0.sh \
tests/head/head-pos.sh \
tests/head/head-write-error.sh \
tests/misc/kill.sh \
--
2.54.0