0

I have a script I run regularly using cron. I would like to get notified by email when these scripts fail. I do not wish to be notified every time they run and produce any output at all.

As such, I am using the script Cronic to run my jobs inside cron, which should mean only error output gets sent, and not just any output.

However, in one script I have a command like this:

if [ "$(ls -A ${local_backup_location}/nextcloud-data/)" ]; then
  # save space by removing diffs older than 6 months
  rdiff-backup --remove-older-than 6M --force ${local_backup_location}/nextcloud-data/ || echo "[$(date "+%Y-%m-%d %T")] No existing nextcloud data backup"
fi

The ls -A ${local_backup_location}/nextcloud-data/ is intended to test if a directory is empty. My problem is that this command seems to result in output which is recognized as error output cronic. Cronic defines an error as any non-trace error output or a non-zero result code. For example:

Cronic detected failure or error output for the command:
/usr/local/sbin/run_backup

RESULT CODE: 0

ERROR OUTPUT: appdata_ocgcv9nemegb files_external flow.log flow.log.1 __groupfolders .htaccess index.html nextcloudadmin nextcloud-db.bak nextcloud.log nextcloud.log.1 .ocdata rdiff-backup-data Test_User updater.log updater-ocgcv9nemegb ] custom gitea-db.sql log ] % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 365 0 0 100 365 0 302 0:00:01 0:00:01 --:--:-- 303 100 365 0 0 100 365 0 165 0:00:02 0:00:02 --:--:-- 165 100 365 0 0 100 365 0 113 0:00:03 0:00:03 --:--:-- 113 100 365 0 0 100 365 0 86 0:00:04 0:00:04 --:--:-- 86 100 365 0 0 100 365 0 70 0:00:05 0:00:05 --:--:-- 70 100 365 0 0 100 365 0 58 0:00:06 0:00:06 --:--:-- 0 100 365 0 0 100 365 0 50 0:00:07 0:00:07 --:--:-- 0 100 365 0 0 100 365 0 44 0:00:08 0:00:08 --:--:-- 0 100 365 0 0 100 365 0 39 0:00:09 0:00:09 --:--:-- 0 100 365 0 0 100 365 0 37 0:00:09 0:00:09 --:--:-- 0 100 10.4M 0 10.4M 100 365 1016k 34 0:00:10 0:00:10 --:--:-- 2493k 100 11.6M 0 11.6M 100 365 1128k 34 0: 00:10 0:00:10 --:--:-- 3547k

STANDARD OUTPUT: Maintenance mode enabled Deleting increment at time: <snip>

So why does the command ls -A ${local_backup_location}/nextcloud-data/ produce error output in this case, and how can I prevent this? An alternative robust method to test if a directory is empty would be acceptable, but I would also like an explanation of why the command seems to produce error output.

EDIT: Adding Cronic stdout with set -ex

Some commenters have requested the actual whole script which is very long, but Cronic reports the actual stdout of the script and I use set -ex at the top of the script. The error output happens immediately after the invocation of ls -A /mnt/reos-storage-2/backups/nextcloud-data/ which is why I believe the error output to be the result of this command.

+ rdiff-backup --ssh-no-compression /var/www/nextcloud /mnt/reos-storage-2/backups/nextcloud/
+ ls -A /mnt/reos-storage-2/backups/nextcloud-data/
+ [ 67cf481e-62a3-1039-8bf2-05805d214bca
<removed>
appdata_ocgcv9nemegb
<removed>
<removed>
<removed>
<removed>
files_external
flow.log
flow.log.1
__groupfolders
.htaccess
index.html
<removed>
<removed>
nextcloudadmin
nextcloud-db.bak
nextcloud.log
nextcloud.log.1
.ocdata
<removed>
<removed>
rdiff-backup-data
<removed>
Test_User
<removed>
updater.log
updater-ocgcv9nemegb ]
+ rdiff-backup --remove-older-than 6M --force /mnt/reos-storage-2/backups/nextcloud-data/
+ date +%Y-%m-%d %T
+ echo [2021-04-21 03:23:38] Starting nextcloud data backup
crobar
  • 223
  • 1
    You say you have a command like that. The command that you show does not produce output. Can you please double check that the actual command is identical to what you show in the question? It looks as if the error output contains an unsorted list of files, which indicates that it's not a list produced by ls. Are you in fact calling find? – Kusalananda Apr 23 '21 at 15:55
  • @Kusalananda it's plausible output for some locales that don't sort non-alphanumerics – Chris Davies Apr 23 '21 at 17:20
  • @roaima Well, it's not the shown command that produces the specific output in any case. Notice how the command substitution is quoted and how the error message contains ] twice and no actual error message. There's also output from curl (?), but no curl command is shown. – Kusalananda Apr 23 '21 at 17:28
  • Ah yes. The last three filenames (custom, gitea-db.sql, log) are out of sequence but the order of the remainder is reproducible under (at least) en_GB.UTF-8. – Chris Davies Apr 23 '21 at 17:39
  • 1
    @crobar, remove any irrelevant parts from that script until you're left with just the part that causes the error. Then post that remaining script in full, including the #!/bin/bash line and any set -o errexit lines etc, and also the cron command you use to launch it. If you think the problem is in the part you quoted, then leave just that, and post the results: the full script and the error output you get. And if the problem doesn't appear after you remove everything else, well, then the problem is somewhere else. – ilkkachu Apr 23 '21 at 19:25
  • My locale is en_GB. I did actually prune the output of the ls command to remove directories with people's names, but I did not change the order of the remaining directories. – crobar Apr 26 '21 at 13:06
  • @Kusalananda, I checked, and turns out you were right that the command shown wasn't the exact command run, I had mixed up the part of the script producing the error, I modified the question to show the exact command producing the error. – crobar Apr 26 '21 at 13:07
  • @crobar, so, now that you've checked, the output posted there is the output you get for that exact script, right? And it doesn't have anything else? You've just edited the script, but not the output, so it's hard to be sure from the face of it that they match each other. Like someone said, the output contains what looks to be a progress report from curl. Does your script use that, or does rdiff-backup use it when running --remove-older-than? As far as I can tell, rdiff-backup does use the rsync libraries, but I can find no mention of curl in connection with it. – ilkkachu Apr 26 '21 at 14:05
  • @ilkkachu, I added the output of the script as reported by Cronic, which also sends the full stdout from the script. I use set -e at the top so all commands are echoed. The error output immediately follows the ls -A command so I made the assumption that it was responsible for this error output. Is this incorrect? – crobar Apr 26 '21 at 14:30
  • Are you always running the script with set -x? Doing so would definitely spam standard error with output. – Kusalananda Apr 26 '21 at 15:11
  • @Kusalananda, yes, I always run with set -x I want the script to stop if there's an error of any kind – crobar Apr 26 '21 at 18:53
  • @crobar, set -x prints all lines the shell runs (the ones starting with the plus). set -e is the one that makes the shell exit if a command returns with an error. (Apart from when it doesn't, see e.g. http://mywiki.wooledge.org/BashFAQ/105 ) – ilkkachu Apr 26 '21 at 19:16
  • @ilkkachu, yes, I was getting mixed up, but yes, I actually want both – crobar Apr 26 '21 at 21:22
  • @crobar, yep, just thought to make sure. – ilkkachu Apr 27 '21 at 09:17

2 Answers2

1
+ [ 67cf481e-62a3-1039-8bf2-05805d214bca
<removed>
[...]
updater.log
updater-ocgcv9nemegb ]

This here, is one single command. It's split to multiple lines in the set -x/xtrace output, because the output from ls contains newlines. (Bash would print that with some quotes, Dash doesn't.)

By default, the xtrace output goes to stderr, the same as the regular error output, Cronic tries to separate them by looking at the + marker at the start of the xtrace lines. That fails here, and it thinks the files names there are part of regular error output.

What cronic does is basically this:

PATTERN="^${PS4:0:1}\\+${PS4:1}"
if grep -aq "$PATTERN" $TRACE
then
    ! grep -av "$PATTERN" $TRACE > $ERR

Disabling xtrace would be one way to fix that, but it would be a shame to do that since cronic supports it so nicely.

Instead, it might be better to use some other way to check if the directory is empty.

Keeping with ls -A, you could pipe the output to wc to count the characters there:

if [ "$(ls -A "${local_backup_location}/nextcloud-data/" | wc -c)" -gt 0 ]; then
    echo "directory not empty"
fi

or to grep:

if ls -A "${local_backup_location}/nextcloud-data/" | grep -q .; then
    echo "directory not empty";
fi

Checking if the directory is empty could be done in other ways, within the shell itself, but handling all the corner cases can be hairy. See e.g. Portable check empty directory

ilkkachu
  • 138,973
-1

If $gitea_backup_dir doesn't exist, or makes ls unhappy in any way, ls will write an error message to STDERR.

You can discard the whole error stream by appending 2>/dev/null to your command.

IMHO, a better way is:

if ([[ -d "$gitea_backup_dir" ]] && \
    [[ $(stat --format="%h" "$gitea_backup_dir") -gt 2 ]] ) ; then
    echo "Nonempty"
else
    echo "Empty"
fi

First, check that the directory exists, then see if the number of hard links in the directory (the number of file and subdirectory entries in the directory) is greater than 2 (every directory has at least 2 entries: . and ..).

waltinator
  • 4,865
  • Or set -- "$gitea_backup_dir"/*; if [ "$#" -gt 0 ]; then ...; fi (assuming nullglob and dotglob are set). – Kusalananda Apr 23 '21 at 16:36
  • looking at the link count can tell if there's other directories inside the directory, but not files, while the ls -A would produce nonempty output if there's anything in the directory, regular files, directories or otherwise, right? – ilkkachu Apr 23 '21 at 19:12
  • Both files in a directory and (sub-)directories in a directory ARE hard links. Don't ask me what ls -A would do. It's easy to set up a test environment and try it. Or read man ls. – waltinator Apr 23 '21 at 19:17
  • @waltinator, (was that a reply to me? Use the @-notation, so that people get notifications.) They said that the ls -A "is intended to test if a directory is empty", and that's what it does in that if there is at least file in the directory, it prints the name. If there isn't, the output is empty. And yes, files are hard links, but they're hard links to themselves, not to the directory. Try e.g. mkdir foo; ls -ld foo (the link count should be 2), touch foo/bar; ls -ld foo (the link count is still 2), and mkdir foo/doo; ls -ld foo (now the link count is 3) – ilkkachu Apr 26 '21 at 13:19