1

I'm new to Bash scripting and am having a difficult time with creating a script that takes all the jpg files from a directory (assuming all of them are jpg) and creating a timeline of images according to their modification date.

I've tried so far:

ls -Rt *.jpg | convert -append jpgfile

and

convert -append $(ls -Rtr $(find MontrealTest -name *.jpg)) MontrealTest.jpg

The bash program should create a timeline of images in q3_image_sorter.bash from oldest to newest. convert -append can be used to create the timeline image made of N image name arguments. N-1 are treated as inputs, to be read and stacked vertically and the final (Nth) argument is the output

filename. time-line image should match the path given as the first argument, but _ to replace any slashes. ex. $ bash q3_image_sorter.bash Q3/SimpleTest outputs file Q3_SimpleTest.jpg. * recommended to useeog to view the files

These are the list of jpg files, and I need to convert them to the images. For example, when I run bash q3_image_sorter.bash Q3/MontrealTest I will get the 8 images from oldest to newest.

enter image description here

mcoder
  • 83
  • 1
  • 8
  • 2
    See [How do I ask and answer homework questions?](https://meta.stackoverflow.com/a/334823/14122) -- you should ask only about a **specific problem** with your **existing implementation**. Posting your full set of requirements is effectively a "plz send me teh codez" question, which typically falls under the close reason "too broad". – Charles Duffy Sep 24 '18 at 01:22
  • BTW, parts of this are duplicative of the existing question [Renaming files in a folder to sequential numbers](https://stackoverflow.com/questions/3211595/renaming-files-in-a-folder-to-sequential-numbers). Showing specifically where you got stuck after trying to apply existing questions' answers would be a step closer to answerability. – Charles Duffy Sep 24 '18 at 01:24
  • Ok thanks, Charles. Sorry, I am just having trouble getting started, as I think I understand the question but probably don't have the syntax right.. – mcoder Sep 24 '18 at 01:31
  • 1
    ...so, the big issue with `ls -Rt` is that `ls` output isn't formatted for software to use programatically -- see [ParsingLs](http://mywiki.wooledge.org/ParsingLs) describing why, and [BashFAQ #3](http://mywiki.wooledge.org/BashFAQ/003) describing how to sort files without it. – Charles Duffy Sep 24 '18 at 01:39
  • Another question is whether `convert` expects to read a list of filenames on stdin -- if not, it won't see thing you pipe to it. – Charles Duffy Sep 24 '18 at 01:40
  • BTW, do your names contain whitespace? If so, the `$(ls ...)` approach runs into the issues described in [BashPitfalls #1](http://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29) as well. – Charles Duffy Sep 24 '18 at 01:41
  • Another question -- is it acceptable if your answer runs only on systems with GNU tools? (As an easy way to tell, if you run `man find`, is there a `-printf` option? Similarly, does `man sort` show an option `--zero-terminated`?) – Charles Duffy Sep 24 '18 at 01:42
  • Ok, I see. The convert takes the image name arguments, I believe from stdin. And no, my names don't contain whitespace. – mcoder Sep 24 '18 at 02:06
  • I can find the -fprint and -fprintf option, but not the -printf. I'm not sure if this makes a difference – mcoder Sep 24 '18 at 02:06
  • ...so, for an academic exercise, it might be safe to make that assertion. In the real world, the worst data loss event I've been present for was caused by someone making assumptions about their filenames (and then a bug is a different piece of software writing to the same directory created a name with a whitespace-surrounded `*`, and the software with the assumption tried to delete it and instead purged the whole directory -- terrabytes of billing backups). Which is to say -- assuming that the world your software will interact with is identical to the world you expect is where bugs come from. – Charles Duffy Sep 24 '18 at 02:07
  • I cannot find --zero-terminated on man sort – mcoder Sep 24 '18 at 02:08
  • Hmm. Out of curiosity, which version does `sort --version` say you have (or does that command throw an error?) – Charles Duffy Sep 24 '18 at 02:12
  • ...that said, we can be safe even without `sort -z` if we just tell `find` to ignore any file with newline literals, so that's doable enough. – Charles Duffy Sep 24 '18 at 02:13
  • That said, I can't find any documentation supporting the assertion that `convert` reads filenames from stdin. https://imagemagick.org/script/convert.php only documents names being received via the command line. – Charles Duffy Sep 24 '18 at 02:22
  • convert can read a list of files from stdin. But you likely need to edit policy.xml file to relax the security restriction on doing that. If you do that, then this should work by adding the stdin `-` as `ls -Rt *.jpg | convert - -append file.jpg`. Otherwise save the list of images to variable: `list=$(ls)` and then do `convert $list -append file.jpg` – fmw42 Sep 24 '18 at 04:09
  • See also https://stackoverflow.com/questions/52461150/stacking-images-in-a-last-modification-timeline-using-linux-command-line-and-ima – fmw42 Sep 24 '18 at 04:15
  • sort (GNU coreutils) 8.25 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . – mcoder Sep 24 '18 at 04:54
  • 1
    To pass a list of filenames to ImageMagick on `stdin`, you need `generate_list | convert @- -append result.jpg` – Mark Setchell Sep 24 '18 at 08:05
  • @fmw42, `list=$(ls)` isn't safe for arbitrary filenames -- both for the reasons described in [ParsingLs](http://mywiki.wooledge.org/ParsingLs), and because the domain of characters that can be included in a filename and the domain of characters that can be stored in a shell variable match: If there's a newline in your results, you don't know if it's literal part of a name, or if it's intended to be a separator between two names. – Charles Duffy Sep 24 '18 at 12:13
  • @mcoder, ...hmm; `-z` / `--zero-terminated` has definitely been in GNU sort *long* before 2016 -- I'm even finding it in a build from 2001. – Charles Duffy Sep 24 '18 at 12:21
  • Actually, what is on my school server is different from my local disk. 2.3-Apple (99) is my local version – mcoder Sep 24 '18 at 22:44
  • Makes sense, then! FYI, you can install GNU tools on a Mac using [Nixpkgs](https://nixos.org/nixpkgs/) (my favorite -- a little more involved to learn, but a better security and consistency model than the others), [MacPorts](https://www.macports.org/), or [Homebrew](https://brew.sh/). – Charles Duffy Sep 25 '18 at 01:00
  • `@MarkSetchell`. Thanks for catching my error. I forgot to include the @ when using stdin with convert. As you point out, it should be @-. – fmw42 Sep 25 '18 at 16:59

1 Answers1

2

Doing this robustly (that is, supporting unusual or hostile file or directory names safely -- except those with newlines, which we discard to avoid a dependency on GNU sort) might look like the following:

#!/usr/bin/env bash

# Start with a version check; this would need changes to support older bash
case $BASH_VERSION in ''|[123].*) echo "ERROR: Bash 4.0 required" >&2; exit 1;; esac

dir=$1
[[ $dir ]] || { echo "Usage: ${0##*/} dirname" >&2; exit 1; }

# telling find to ignore files with newlines so we don't need to use GNU sort
readarray -t files < <(
  find "$dir" -name $'*\n*' -prune -o -type f -name '*.jpg' -printf '%T@ %p\n' \
    | sort -n | sed -e 's/^[^ ]* //'
)

(( ${#files[@]} )) || { echo "ERROR: No .jpg files found in $dir" >&2; exit 1; }

outname=${dir//'/'/_}.jpg  ## replace slashes with underscores and add .jpg to get outname

convert "${files[@]}" -append "$outname"

References for the syntax used in each location:

  • readarray reads a list of items into an array. By default, it expects newline-delimited content; in bash 4.4, the -d '' argument can be used to specify NUL delimiters instead. readarray is also named mapfile, and is documented at http://wiki.bash-hackers.org/commands/builtin/mapfile
  • (( )) enters an arithmetic context, where a zero result evaluates as false and a nonzero result identifies as true. See http://wiki.bash-hackers.org/syntax/arith_expr for more on arithmetic expressions in bash.
  • ${#arrayname[@]} expands to the number of items in the array named arrayname. See BashFAQ #5 for a general introduction to arrays in bash, or http://wiki.bash-hackers.org/syntax/arrays
  • ${varname//search/replace} expands to the contents of the variable named varname, with all instances of search replaced with replace. This is an example of parameter expansion, described in detail at http://wiki.bash-hackers.org/syntax/pe.
  • "${arrayname[@]}" expands to the individual items from the array named arrayname.
  • <( ... ) is a process substitution -- syntax which creates a file-like object which, when read, will contain the output of the command ....
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thank you, Charles. Due to time constraints and me not being too familiar with readarray I've decided to use $(find "$dir" -name '*.jpg') and then recursively sorting and calling convert -append. However, I'll keep your advice in mind when doing something similar in the future. Thanks so much! – mcoder Sep 25 '18 at 02:46
  • 1
    Please do review [BashPitfalls #1](http://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29) about that approach. – Charles Duffy Sep 25 '18 at 03:37