How to both show a list of files and a `cat` of one

Sorry folks. The brain is on holiday just now. :wink:

I am stuck at the concept stage of the problem.

If a machine reboots (monthly) it creates a backup of the log file (and time stamps it at NOW then starts a new log file.

Existing:
I get a list of files in that directory. Not exciting and this is a nominal view:

As said: not exciting.
But if other reboots happened to occur this month, they would be show with time stamped file names. Which would indicate when the machine had rebooted.

PROBLEM - as I see it.
The last_alive.db file.
That is an overwritten file showing the uptime.
So I'm wanting to get that file's contents shown rather than it's name.
ie:

cat last_alive.db 
HEARTBEAT
up 19 weeks, 6 days, 13 hours, 33 minutes

So the last (shown) line is the one I want to get/see.

I'm guessing it is not going to be easy.

Example of other months

pi@BedPi:/media/pi/06BB-C87D/logs/reboot/2025-06 $ ls
'Rebooted at 2025-06-04 175552.db'  'Rebooted at 2025-06-09 101721.db'
'Rebooted at 2025-06-04 225056.db'  'Rebooted at 2025-06-09 102751.db'
'Rebooted at 2025-06-04 234148.db'  'Rebooted at 2025-06-09 104929.db'
'Rebooted at 2025-06-05 202521.db'  'Rebooted at 2025-06-09 115023.db'
'Rebooted at 2025-06-06 180203.db'  'Rebooted at 2025-06-09 185031.db'
'Rebooted at 2025-06-06 183946.db'  'Rebooted at 2025-06-12 144016.db'
'Rebooted at 2025-06-07 070837.db'  'Rebooted at 2025-06-12 144153.db'
'Rebooted at 2025-06-08 182600.db'  'Rebooted at 2025-06-12 151600.db'
'Rebooted at 2025-06-08 191137.db'

So in/on that month I would get/see those file NAMES listed, and at the top the current last line of the last_alive.db file.

Thoughts?

Off the top of my head and completely untested ...

Read in the whole file as a string. Split the string on /n (linefeed character). Now you have an array. Throw away the first line (arrayvar.splice(1)?). Use arrayvar.replace to get rid of fixed text and use arrayvar.split(', ') to get a new array with values. You could do all that with a single regex if you prefer.

For the rest, I'd use an ls output - or, better use a filing system node or add node:fs as a package to a function node and use node.js's fs tools. You just need a list of the file names and then extract the date/time.

1 Like

THANKS!

That is the idea which kinda came into my head.

Working on it with Mr ChatGPT just now.

Well...
You want (a) the last line of the most recent file in that directory 2025-06?
Or you want (b) the last line of the most recent file in any directory below /media/pi/06BB-C87D/logs/reboot?

You made your life harder by including spaces in your filenames!
It looks like directory and file names can be sorted in date order.

Does find /media/pi/06BB-C87D/logs/reboot -name "Rebooted at*" -print | sort | tail -1 give you the name of the most recent log file?

If so, the last line in that file should be given by tail -1 $(find /media/pi/06BB-C87D/logs/reboot -name "Rebooted at*" -print | sort | tail -1)

1 Like

I guess I should also ask what this whole palaver gives you.
Is the output of uptime --since useful?

This is what "we" (Mr GPT and I) got after a LOT of messing around.

It got into hallucination mode and went around quite a number of times with the same reply.

Then I started to better understand what it was TRYING to do and tried a few things myself.

Sorry, this code has a path set for my TEST case but is easily modified.

Code - no foreigners in it.

[{"id":"5eea7184f98a4c51","type":"exec","z":"e891eac8.28aea8","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"List reboot files (newest first)","x":640,"y":2830,"wires":[["ac0cc1d229a3949c"],[],[]]},{"id":"f8c34d36176bb3de","type":"template","z":"e891eac8.28aea8","name":"cmd","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"find {{{basePath}}} -maxdepth 1 -type f ! -name 'last_alive.db' -printf '%f\\n' | sort -r","output":"str","x":430,"y":2830,"wires":[["9521311b0c077153","5eea7184f98a4c51"]]},{"id":"ac0cc1d229a3949c","type":"function","z":"e891eac8.28aea8","name":"Tag reboot file","func":"// Tag reboot file lines and prepare for merging\n\n// split payload into lines, trim whitespace, remove empty lines\nlet lines = msg.payload.split(/\\r?\\n/).map(l => l.trim()).filter(l => l !== '')\n\n// prepend 'Rebooted at ' only if not already present\nlines = lines.map(l => l.startsWith('Rebooted at') ? l : `Rebooted at ${l}`)\n\n// rejoin lines into single payload\nmsg.payload = lines.join('\\n')\n\n// mark this message as order 1 (after uptime)\nmsg.order = 1\n\n// if no lines left, mark as empty so Merge node can skip it\nif (lines.length === 0) {\n    msg._emptyForMerge = true\n}\n\nreturn msg\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":2830,"wires":[["018e232e3d83ec77","5219da2fc0395841"]]},{"id":"ed5f05a749fdb98b","type":"junction","z":"e891eac8.28aea8","x":310,"y":2860,"wires":[["f8c34d36176bb3de","45b84c6df27ea117","c53daecf90b70618"]]},{"id":"9521311b0c077153","type":"debug","z":"e891eac8.28aea8","name":"debug 18","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":2780,"wires":[]},{"id":"018e232e3d83ec77","type":"function","z":"e891eac8.28aea8","name":"Merge last_alive + reboots","func":"// --- Explicit context wipe ---\nif (msg.wipe === true) {\n    context.set('store', {})\n    node.warn('Context wiped via msg.wipe')\n    return null\n}\n\n// --- Get or initialise context store ---\nlet store = context.get('store') || {}\n\n// --- Require valid order ---\nif (msg.order !== 0 && msg.order !== 1) {\n    node.warn('Invalid or missing msg.order')\n    return null\n}\n\n// --- Store message by order (arrival order irrelevant) ---\nstore[msg.order] = RED.util.cloneMessage(msg)\ncontext.set('store', store)\n\n// --- Wait until both messages are present ---\nif (!store[0] || !store[1]) {\n    return null\n}\n\n// --- Prepare output messages ---\nconst msg0 = RED.util.cloneMessage(store[0])\nconst msg1 = RED.util.cloneMessage(store[1])\n\nmsg0.topic = 'uptime_and_reboots'\nmsg1.topic = 'uptime_and_reboots'\n\n// --- Send BOTH messages from ONE output ---\nnode.warn('Sending msg.order 0 then msg.order 1')\nreturn [[msg0, msg1]]\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1120,"y":2860,"wires":[["9f41501f4fbd6cb7"]]},{"id":"5219da2fc0395841","type":"debug","z":"e891eac8.28aea8","name":"msg1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":910,"y":2790,"wires":[]},{"id":"378e0fe5313bc482","type":"change","z":"e891eac8.28aea8","name":"","rules":[{"t":"set","p":"basePath","pt":"msg","to":"/media/pi/06BB-C87D/logs/reboot/2025-06/","tot":"str"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":200,"y":2860,"wires":[["ed5f05a749fdb98b"]]},{"id":"45b84c6df27ea117","type":"template","z":"e891eac8.28aea8","name":"file","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{{basePath}}}last_alive.db","output":"str","x":430,"y":2890,"wires":[["a06b52bf2cd0ada2","5d69e6136bd43607"]]},{"id":"c53daecf90b70618","type":"debug","z":"e891eac8.28aea8","name":"base path","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"basePath","targetType":"msg","statusVal":"","statusType":"auto","x":340,"y":2940,"wires":[]},{"id":"aa1cd627e0d32f80","type":"function","z":"e891eac8.28aea8","name":"Extract uptime","func":"// extract line starting with 'up ' from last_alive.db\nconst lines = msg.payload.split(/\\r?\\n/)\nconst uptime = lines.find(l => l.startsWith('up '))\nmsg.payload = uptime || 'uptime not found'\nmsg.order = 0 // last_alive comes first\nreturn msg","outputs":1,"x":850,"y":2890,"wires":[["018e232e3d83ec77","3b146b0e65398225"]]},{"id":"b60a88c8903619e9","type":"inject","z":"e891eac8.28aea8","name":"Wipe","props":[{"p":"wipe","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":890,"y":2990,"wires":[["018e232e3d83ec77"]]},{"id":"9f41501f4fbd6cb7","type":"debug","z":"e891eac8.28aea8","name":"Final output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1080,"y":2920,"wires":[]},{"id":"09e98bbe33888ffa","type":"inject","z":"e891eac8.28aea8","name":"Trigger","props":[{"p":"basePath","v":"/media/pi/06BB-C87D/logs/reboot/2025-06/","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":190,"y":2810,"wires":[["378e0fe5313bc482"]]},{"id":"a06b52bf2cd0ada2","type":"file in","z":"e891eac8.28aea8","name":"Read last_alive.db","filename":"payload","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"utf8","allProps":false,"x":610,"y":2890,"wires":[["aa1cd627e0d32f80"]]},{"id":"5d69e6136bd43607","type":"debug","z":"e891eac8.28aea8","name":"last alive","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":2930,"wires":[]},{"id":"3b146b0e65398225","type":"debug","z":"e891eac8.28aea8","name":"msg2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":880,"y":2930,"wires":[]}]

NEEDED files:

last_alive.db is the current active file which shows the uptime.
Other files (which may or not exist) are any other files which were renamed if the machinereboots in THIS MONTH ONLY.
At the start of each month, all existing files are moved to their respective month directories. (Or is it just before the end of the month? Does it matter?)

The output is then sent to a ring buffer node and I hope all are on their own line.

The only catch is the sorting of the other files and getting them to be NEWEST AT THE TOP sorting.
So the current (active) is at the top, then a list of all reboots for this month are shown below starting with the most recent to the oldest (at the bottom).

I spent about 3 hours (maybe more) today earlier and got it to this.

This is what I have in my test directory (as per the code)
Files:

 last_alive.db                      'Rebooted at 2025-06-08 191137.db'
'Rebooted at 2025-06-04 175552.db'  'Rebooted at 2025-06-09 101721.db'
'Rebooted at 2025-06-04 225056.db'  'Rebooted at 2025-06-09 102751.db'
'Rebooted at 2025-06-04 234148.db'  'Rebooted at 2025-06-09 104929.db'
'Rebooted at 2025-06-05 202521.db'  'Rebooted at 2025-06-09 115023.db'
'Rebooted at 2025-06-06 180203.db'  'Rebooted at 2025-06-09 185031.db'
'Rebooted at 2025-06-06 183946.db'  'Rebooted at 2025-06-12 144016.db'
'Rebooted at 2025-06-07 070837.db'  'Rebooted at 2025-06-12 144153.db'
'Rebooted at 2025-06-08 182600.db'  'Rebooted at 2025-06-12 151600.db'

The last_alive.db is a copy of the real one. But as it is needed, I had to put it there.

Basic structure:

cat Rebooted\ at\ 2025-06-04\ 175552.db 
2025-06-04 17:53:05
up 35 weeks, 5 days, 22 hours

and

cat last_alive.db 
HEARTBEAT
up 19 weeks, 6 days, 13 hours, 46 minutes

In reply to the last question:
This is what I get for this example.
Two messages:

up 19 weeks, 6 days, 13 hours, 46 minutes

And - needs work.

Rebooted at 2025-06-12 151600.db
Rebooted at 2025-06-12 144153.db
Rebooted at 2025-06-12 144016.db
Rebooted at 2025-06-09 185031.db
Rebooted at 2025-06-09 115023.db
Rebooted at 2025-06-09 104929.db
Rebooted at 2025-06-09 102751.db
Rebooted at 2025-06-09 101721.db
Rebooted at 2025-06-08 191137.db
Rebooted at 2025-06-08 182600.db
Rebooted at 2025-06-07 070837.db
Rebooted at 2025-06-06 183946.db
Rebooted at 2025-06-06 180203.db
Rebooted at 2025-06-05 202521.db
Rebooted at 2025-06-04 234148.db
Rebooted at 2025-06-04 225056.db
Rebooted at 2025-06-04 175552.db

Ideally each of those should be individual messages.
But at this stage: not critical.

Then they are displayed on/in a table on the dashboard.

(Example of what I want)

But in this machine's case there is/was only 1 reboot this month.
So it looks ok.
I'd prefer it to be like the commands received list.
Each entry in/on it's own line.

Is the command
journalctl --list-boots --no-pager
any use to you?

1 Like

Never even heard of that one.

I'll look into it.

pi@TelePi:~ $ journalctl --list-boots --no-pager
 0 e38adcfae09042a9b43c0bb79323f049 Sun 2026-02-08 07:16:26 AEDT—Mon 2026-02-09 06:18:34 AEDT

Looking above, not sure what that is telling me.
But below is what I'm told with my system.

pi@BedPi:~ $ journalctl --list-boots --no-pager
 0 1de0c2c6667f44febb74e5b9eb484e65 Sat 2026-02-07 22:58:01 AEDT—Mon 2026-02-09 06:19:06 AEDT

On this machine, it is different but I seem to be given the same amount of information.

So I'm not sure the command covers my needs in this case.

I think that the issue with journalctl is that logs are not necessarily persisted to files. On my system, like yours, the output is meaningless since my last reboot was at 2025-07-23T14:06:47!!

For Debian systems at least, you can use the command

sudo utmpdump /var/log/wtmp | grep reboot

The raw output shows you not only boots but also all logins and some other stuff, hence the grep.

In theory, this file gets rotated periodically so I guess on a heavily used system, you might also have to check archived logs as well. On my system though, the first reboot entry is 2020-02-08T15:54:41 :rofl:

This search may help if you do need to also review archives: