{"id":302,"date":"2026-10-13T11:21:19","date_gmt":"2026-10-13T11:21:19","guid":{"rendered":"http:\/\/www.scmgalaxy.com\/tutorials\/2011\/11\/22\/checking-file-attributes-in-perl\/"},"modified":"2026-10-13T11:21:19","modified_gmt":"2026-10-13T11:21:19","slug":"checking-file-attributes-in-perl","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/checking-file-attributes-in-perl\/","title":{"rendered":"Perl File Test Operators Explained for DevOps Automation"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2011\/11\/file-attributes-in-perl.png\" alt=\"Perl file attributes quick reference\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Originally published on 2011-11-22; fully revised and updated.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you write Perl for build, release, or ops automation, file test operators are one of the fastest ways to make decisions in scripts. This walkthrough shows what each common operator does, how permission checks differ for real and effective IDs, and how to structure reliable file-attribute checks in automation flows.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Perl file test operators at a glance<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Perl file tests are unary operators such as <strong>-e<\/strong>, <strong>-f<\/strong>, and <strong>-x<\/strong>. They can test a path directly or reuse the previous file status result via <strong>_<\/strong> (underscore), which helps when you run several checks on the same file.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Operator<\/th><th>What it checks<\/th><\/tr><\/thead><tbody><tr><td>-e<\/td><td>Path exists<\/td><\/tr><tr><td>-f<\/td><td>Plain file<\/td><\/tr><tr><td>-d<\/td><td>Directory<\/td><\/tr><tr><td>-l<\/td><td>Symbolic link<\/td><\/tr><tr><td>-p<\/td><td>Named pipe<\/td><\/tr><tr><td>-S<\/td><td>Socket<\/td><\/tr><tr><td>-b<\/td><td>Block special file<\/td><\/tr><tr><td>-c<\/td><td>Character special file<\/td><\/tr><tr><td>-t<\/td><td>Filehandle is a TTY<\/td><\/tr><tr><td>-s<\/td><td>File size in bytes (zero means empty)<\/td><\/tr><tr><td>-z<\/td><td>File size is zero<\/td><\/tr><tr><td>-u<\/td><td>setuid bit is set<\/td><\/tr><tr><td>-g<\/td><td>setgid bit is set<\/td><\/tr><tr><td>-k<\/td><td>Sticky bit is set<\/td><\/tr><tr><td>-T<\/td><td>Looks like a text file (heuristic)<\/td><\/tr><tr><td>-B<\/td><td>Looks like a binary file (heuristic)<\/td><\/tr><tr><td>-r \/ -w \/ -x<\/td><td>Readable \/ writable \/ executable by effective user or group ID<\/td><\/tr><tr><td>-R \/ -W \/ -X<\/td><td>Readable \/ writable \/ executable by real user or group ID<\/td><\/tr><tr><td>-o<\/td><td>Owned by effective user ID<\/td><\/tr><tr><td>-O<\/td><td>Owned by real user ID<\/td><\/tr><tr><td>-A<\/td><td>Age in days since last access (measured from script start time)<\/td><\/tr><tr><td>-M<\/td><td>Age in days since last modification (measured from script start time)<\/td><\/tr><tr><td>-C<\/td><td>Age in days since inode\/status change (measured from script start time)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Real ID vs effective ID permission checks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One of the most important distinctions is lowercase versus uppercase permission operators. Lowercase checks effective IDs; uppercase checks real IDs.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Operator pair<\/th><th>Checks against<\/th><th>Typical use<\/th><\/tr><\/thead><tbody><tr><td>-r vs -R<\/td><td>Effective IDs vs real IDs<\/td><td>Runtime access checks, especially in privileged contexts<\/td><\/tr><tr><td>-w vs -W<\/td><td>Effective IDs vs real IDs<\/td><td>Write-safety checks before creating or updating files<\/td><\/tr><tr><td>-x vs -X<\/td><td>Effective IDs vs real IDs<\/td><td>Verifying executability of scripts or binaries<\/td><\/tr><tr><td>-o vs -O<\/td><td>Effective UID vs real UID ownership<\/td><td>Ownership-sensitive logic in wrappers or elevated scripts<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Example script: describe a path with multiple attribute checks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This example uses <strong>_<\/strong> to reuse the last file status check after <strong>-e<\/strong>, reducing repeated path resolution for subsequent tests.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-perl\"><span class=\"hljs-comment\">#!\/usr\/bin\/env perl<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">strict<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">warnings<\/span>;\n\nmy $file = shift @ARGV <span class=\"hljs-keyword\">or<\/span> <span class=\"hljs-keyword\">die<\/span> <span class=\"hljs-string\">\"Usage: $0 &lt;path&gt;\\n\"<\/span>;\nmy @description;\nmy $size;\n\n<span class=\"hljs-keyword\">if<\/span> (-e $file) {\n    push @description, <span class=\"hljs-string\">'binary'<\/span> <span class=\"hljs-keyword\">if<\/span> -B _;\n    push @description, <span class=\"hljs-string\">'socket'<\/span> <span class=\"hljs-keyword\">if<\/span> -S _;\n    push @description, <span class=\"hljs-string\">'text file'<\/span> <span class=\"hljs-keyword\">if<\/span> -T _;\n    push @description, <span class=\"hljs-string\">'block special file'<\/span> <span class=\"hljs-keyword\">if<\/span> -b _;\n    push @description, <span class=\"hljs-string\">'character special file'<\/span> <span class=\"hljs-keyword\">if<\/span> -c _;\n    push @description, <span class=\"hljs-string\">'directory'<\/span> <span class=\"hljs-keyword\">if<\/span> -d _;\n    push @description, <span class=\"hljs-string\">'plain file'<\/span> <span class=\"hljs-keyword\">if<\/span> -f _;\n    push @description, <span class=\"hljs-string\">'executable'<\/span> <span class=\"hljs-keyword\">if<\/span> -x _;\n\n    $size = -s _;\n    push @description, defined $size ? ($size ? <span class=\"hljs-string\">\"$size bytes\"<\/span> : <span class=\"hljs-string\">'empty'<\/span>) : <span class=\"hljs-string\">'size unknown'<\/span>;\n\n    <span class=\"hljs-keyword\">print<\/span> <span class=\"hljs-string\">\"$file is \"<\/span>, join(<span class=\"hljs-string\">', '<\/span>, @description), <span class=\"hljs-string\">\"\\n\"<\/span>;\n} <span class=\"hljs-keyword\">else<\/span> {\n    <span class=\"hljs-keyword\">print<\/span> <span class=\"hljs-string\">\"$file does not exist\\n\"<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs language-bash\">perl file_attrs.pl \/etc\/passwd<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Workflow: where file checks fit in an automation pipeline<\/h2>\n\n\n\n<pre class=\"mermaid\">\nflowchart TD\n    A[Input path from script argument] --> B[Test existence with -e]\n    B --> C{Path exists}\n    C -- no --> D[Exit with clear error]\n    C -- yes --> E[Run type and permission tests]\n    E --> F[Reuse cached stat result with underscore]\n    F --> G[Build classification and size output]\n    G --> H[Decide next automation step]\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Practical pattern for deployment scripts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For release automation, gate file operations on existence, readability, and non-zero size before continuing:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-perl\"><span class=\"hljs-keyword\">if<\/span> (-f $artifact &amp;&amp; -r _ &amp;&amp; -s _) {\n    <span class=\"hljs-keyword\">print<\/span> <span class=\"hljs-string\">\"Artifact is present and readable\\n\"<\/span>;\n    <span class=\"hljs-comment\"># continue deployment<\/span>\n} <span class=\"hljs-keyword\">else<\/span> {\n    <span class=\"hljs-keyword\">die<\/span> <span class=\"hljs-string\">\"Artifact missing, unreadable, or empty\\n\"<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Common pitfalls and best practices<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Prefer Perl file tests over parsing <code>ls -l<\/code> output in shell commands.<\/li><li>Be explicit about real versus effective ID checks when scripts may run with elevated privileges.<\/li><li>Remember that <code>-T<\/code> and <code>-B<\/code> are heuristic, not strict MIME detection.<\/li><li>Use underscore caching (<code>_<\/code>) when checking many attributes on the same path.<\/li><li>Treat permissions and special-bit checks as platform-sensitive, especially outside Unix-like systems.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Original article: <a href=\"https:\/\/www.devopsschool.com\/blog\/checking-file-attributes-in-perl\/\">How to Check File Attributes in Perl<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Perl\u2019s built-in file test operators let you check existence, type, size, permissions, and ownership without external commands. This guide explains the operators, real vs effective ID checks, and production-friendly patterns for automation scripts.<\/p>\n","protected":false},"author":1,"featured_media":3879,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[14],"tags":[2679,438,349,2680,2681,1122,545,175,2683,2684,2682,2685,2686,2687,2578,213],"class_list":["post-302","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-perl","tag-attributes","tag-check","tag-file","tag-file-attributes","tag-file-attributes-in-perl","tag-guide","tag-how","tag-perl","tag-perl-attributes","tag-perl-attributes-explained","tag-perl-attributes-tutorial","tag-perl-file-attributes-explained","tag-perl-file-attributes-guide","tag-perl-file-attributes-reference","tag-perl-tutorial","tag-tutorial"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/302","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=302"}],"version-history":[{"count":3,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/302\/revisions"}],"predecessor-version":[{"id":79152,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/302\/revisions\/79152"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media\/3879"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=302"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=302"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=302"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}