readFile cannot find file when parameter is passed by variable

I’ve been scanning through the boards and I can’t seem to find anything on this. Hopefully someone here as seen this before?

I’m trying to dynamically include a content file on a list page based on the path currently being browsed. I can generate the string just fine (minor quip: please, there has got to be a better string concat than printf…), but for some reason, readFile always generates “file does not exist”.

The code in question:

{{ $album := path.Dir .Page.RelPermalink }}
{{ $contpath := printf $album | printf “%s%s” $album | printf “/” }}
{{ $adtlcont := readFile $contpath }}

The desired pattern would look like: “/content/folder/”

If I hard-code the value (eg. readFile “/content/folder/”), it works, as it should.
If I hard-code the value to a variable and pass that to readFile:

{{ $contpath := “/content/folder/” }}
{{ $adtlcont := readFile $contpath }}

This also works.

When I print out the contents of the dynamically-created $contpath, it appears to be fine. A countrunes shows that I’ve got no extra characters. Even fileExists says that the file exists when it’s in variable form. But readFile? Bzzzt.

Does anyone know what possible issue there could be that I’m somehow missing?

Many thanks!

I’ve run into similar behaviour with imageConfig vs. fileExists:

fileExists looks at the OS file system, not Hugo’s virtual file system

So, my question is whether /content/folder/ exists in a different spot in your mounts than it does on the actual filesystem.

Yeah, I thought about that, which is why I tried both hard-coding the actual fileRead vs. passing it a value via a variable. And, so long as the value is hard-coded (being it in the fileRead or in a variable), it works. It’s only when I dynamically construct the value (which looks identical to me every time I look) that it breaks.

Now, here’s where things get REALLY weird. I just tried this – mostly to test your point about fileExists looking elsewhere – so now the code reads:

{{ $album := path.Dir .Page.RelPermalink }}
{{ $contpath := (printf $album | printf “%s%s” $album | printf “/”) | chomp }}
{{ if fileExists $contpath }}
{{ $adtlcont := readFile $contpath }}

And guess what? It works. All I did was add the fileExists.

If you want me, I’ll be over in the corner sobbing…

1 Like

(Oh, and yes, I did add chomp, but mostly as an earlier diagnostic to see if there was an errant whitespace. countrunes pretty much answered that one…)

Are you on Windows, Mac or Linux? I wonder if there was some weird newline thing (and count runes just counted it as one line break regardless of ending type). I’d hope not, but…

I’d be curious about the actual character by character code points…maybe a Unicode-16 vs UTF-8 issue? Again, weird if so, but I’m grasping at straws.

I’m with on the straws, too. Mac, FWIW. When I throw this up through Github Actions, it’s running on Ubuntu with similar results.

Know any handy methods for getting the charset encoding of a character? :wink:

Hmmm… $album is the only ‘external’ character source. I wonder what code points it is producing. Theory: fileExists results in path normalization which is somehow not present even though the string looks fine.

It seems a sound theory. It’s certainly the one I’m going with!

1 Like

Wait. What pages is $contpath being generated for? Is there only one page that could be in play, or is Hugo choking on a page other than the page you think it is choking on?

Only one path, the concat is putting together “/content/(album)/(album).md” based on the string I get from .Page.RelPermalink.

How are you ensuring this code only runs for the page at /content/(album)/(album).md? (Starting with exactly one album directory and one, for testing purposes?)

Yes, and it’s a “flat” site – only one level of path I have to worry about (eg. there won’t be a case of /album/subalbum/)

Has this been resolved?


You can use any of these variadic[1] template functions:

{{ add "a" "/" "b" "/" "c" }}       --> a/b/c
{{ print "a" "/" "b" "/" "c" }}     --> a/b/c
{{ printf "%s/%s/%s" "a" "b" "c" }} --> a/b/c
{{ path.Join "a" "b" "c" }}         --> a/b/c

  1. Variadic function - Wikipedia ↩︎

1 Like

Resolved-ish. For whatever reason, a direct readFile breaks; if you do a fileExists first and then do a readFile, it’s fine.

Good to know about add!! I somehow missed that (doh) and will definitely take that up, thanks!!

So, that doesn’t make much sense to me, and I cannot reproduce. Maybe there’s a timing issue with your scripted build (i.e., file is not available yet), but that’s a bit of a stretch.

Using .RelPermalink to get a path is a bit fragile (spaces, capitalization, permalink settings, etc.), and os.ReadFile is clumsy when compared to page resources.


  1. Instead of content/album/album.txt, why not content/album/ Then you can just render .Content provided there’s some minimal front matter (including just delimeters).

  2. Use your existing file structure (content/album/album.txt), but treat it as a page resource.

    {{ with .Resources.GetMatch "*.txt" }}
      {{ .Content | $.RenderString }}
    {{ end }}
1 Like

Hmm. I don’t think it’s a timing thing, the file being read pre-exists.

I’ll freely admit that it should be as you describe with files, but this is a bit of a (regrettable) afterthought about how I built the site, which works as follows:

  • Scan folder (/assets/photos/*) for pictures (this is a core requirement for my needs, once this is “done”, I just want to add pictures, nothing else)
  • Build data files from photo EXIF data (yes, I know Hugo has EXIF functions, but I can’t get enough data out of it, so I have a pre-processor)
  • Build pages from data files (using Hugo Data to Pages)
  • Build site (this is where the readFile error occurs)

I tried adding to the appropriate folders, but I realized (only after the fact) that I’m relying on a list template that snags all the children (the pictures) of a given album, and putting in threw me off. I just wanted to insert some simple text at the top of the existing list, so I modded the template.

Yes, you may shake your head at the n00b move. :wink: TBH I was tired of the small tweaks and wanted to get back to the (arduous) exercise of populating with photos.

I may return to this later and tweak the templates so it’s more Hugo and less hack.

Just FYI. For this page…

The only metadata not available through Hugo’s image .Exif method is color temperature (it’s an XMP tag, not an EXIF tag). If you can live without that tag, it seems like it would be a lot simpler (with fewer moving parts) to let Hugo do all the work.

Reeeeeeally… Well, maybe I do need to re-look. I was having a heck of a time with some of the other data (like lens, in particular).