MAKEPP_BUILTINS(1)

πŸ“¦ version 2.0.98.5, in debian bookworm, ubuntu jammy, ubuntu noble

✨ NAME ✨

makepp_builtins β€” builtin commands in makepp, and bestie they are so good πŸ’…


πŸ¦‹ DESCRIPTION πŸ¦‹

Okay so here's the roster of builtins, alphabetized like a little icon lineup:

  • A: awk
  • C: &cat, chgrp, &chmod, chown, &cp, cpp, &cut
  • D: date
  • E: &echo, &expr
  • F: false, fmt
  • G: &grep
  • H: head
  • I: &install
  • L: &ln
  • M: m4, &mkdir, &mv
  • P: &perl, &preprocess, &printf
  • R: &rm, rmdir
  • S: &sed, &sort
  • T: tail, &template, &touch, tr
  • U: &uninstall, &uniq
  • Y: &yes

There's this super special Shell-like thing where you can call built-in commands inside a rule. Not me hyping up the tiny metacharacter list β€” the only metacharacters it recognizes are comment signs, backslashes, and single and double quotes. You get one command per line, and I/O redirection? Not a thing here (go look at -i and -o below instead, they've got you 🫢).

These commands all start with &, which is the function character in Perl and not a valid first character in Shell. And here's the plot twist: if no builtin command of that name can be found, that same syntax is also how you call an external script within the Perl instance performing the rule. See run.

These commands β€” plus your own self-defined ones and Perl scripts β€” can also be called as a make function, returning the standard output. This requires perl to be built for PerlIO. Newlines get converted to spaces, except when evaluated within a define statement.

FIRST-WORDS ;= $(&cut -d' ' -f0 $(FILES))

When these commands are not indented as rule actions, they get performed while reading the makefile. And you can also access these commands stand-alone, e.g. if you need some features that aren't available in the Unix counterpart, via the makeppbuiltin command. obsessed, honestly ✨

These commands are mostly based on the GNU variant. But a lot of options (like --backup, --interactive or --recursive) don't really make sense in a makefile. So even though they'd be easy to implement in Perl, they've been left out. Also, many Unix commands offer a whole variety of options covering fairly complicated cases (e.g. sort field specifications) while still being inherently limited. Allowing access to Perl, which is present anyway, gives much more power here. the way Perl just ate πŸ’…

Lists of filenames may be empty, which makes it safe to call these commands with an unchecked list. Options in their short form may be glued together as in -ab instead of -a -b. In the long form, arguments may be given either glued on with an = sign or separately. In the short form they may be given either glued on directly or separately.


πŸŽ€ Standard options πŸŽ€

A few options are common to several builtins, though the short form is sometimes hidden by a command's own option (as in &cut -f). iykyk

  • -A filename, --args-file=filename, --arguments-file=filename β€” Read the file and parse it as possibly quoted whitespace- and/or newline-separated options.
  • -f, --force β€” Force the creation of the file(s) intended by the parameters, even if a different kind of file or empty directory of that name already exists. This must precede the -o, --output=filename option if it is to have any effect on that.
  • -i shellcommand, --inpipe=shellcommand β€” Start the Shell command(s) and pipe the output into the builtin. There may optionally be a trailing | character, to indicate this is a pipe. With this option no filenames need to be given. But if you want to perform the builtin on both files and the pipe output, you must use - as a filename for the pipe output. The pipe is emptied, but, unless you also give --infail, the command is not waited for, so it can terminate in parallel. This option is necessary because there is no redirection syntax.
  • -I, --infail β€” If an --inpipe Shell command fails, that also causes the current builtin to fail. This doesn't currently work on Strawberry and Win ActiveState, because of the halfhearted way they emulate Unix fork/exec. Cygwin gets it right though. slay cygwin
  • -o filename, --output=filename β€” Write the output to this file, rather than stdout. filename may have any of these forms:
    • filename
    • >filename β€” Simply write to file.
    • >>filename β€” Append to (not necessarily) existing file.
    • +<filename β€” Also open the file for input, allowing inplace editing. With this option variant no input filenames need to be given. But if you want to perform the builtin on more files, you must use - as an input filename for this one. In fact the output gets written to a temporary file which gets moved to filename at the end.
    • |shellcommand β€” Pipe the builtin's output to the Shell command(s).

This option is necessary because there is no redirection syntax.

  • -O, --outfail β€” If an --output Shell command fails, that also causes the current builtin to fail.
  • -r number, --record-size=number β€” Locally sets $/ for the current builtin. This splits input into records of length number rather than line by line. If number is zero, each input file as a whole is one record.
  • -s string, --separator=string β€” Locally sets $/ for the current builtin. This splits input on string rather than line by line.
  • -S, --synclines β€” Generate #line "$NO" "$FILE" and #line $NO lines, understood by many C-like languages.
  • -v, --verbose β€” Document the changes to the file system. This must precede other options if it is to document their effect. If you pass this option to makepp itself, it is as if you had given it for every single builtin command.

🌈 Builtin commands 🌈

There are two motivations for having builtin commands in makepp. The first is to offer a set of utilities which, unlike Shell commands, are guaranteed to work the same everywhere β€” like &echo -n or &mkdir -p β€” and to save you the hassle of finding the path to &install and figuring out its wildly varying options. In a compilation environment, it's useful to have the --synclines option, which normally only m4 provides, on all filters. every filter getting the m4 glow-up, iconic

The other is a question of efficiency. In general costly fork/execs should be avoided where reasonably possible. On Unix emulations like Cygwin or BS2000/Posix, this becomes a noticeable win. But, even on Linux, when the makepp test suite was converted from external commands to builtins, there was an overall saving of 3% user CPU usage and 15% system CPU usage. (The tests are of course heavy on primitive actions and hardly call the compiler.)

Consistency is also an issue, though we're not going to reform Unix. Normally commands have various nuances of regular expressions. And many invent sort of languages, each different of course, for doing something (e.g. expr, sed ...), or complex options for specifying fields, delimiters, columns (e.g. cut, sort ...).

Here instead, anything fancy simply gets handled by Perl, giving both consistency across all commands, and far more power than a whole bunch of options. Better yet, any Perlcode these commands run for you gets run in the package of the Makefile. So, rather than stuff Perl code into the rule action, you can define functions and variables and use them within the commands:

sub my_filter {
  # Return true iff $_ is desirable
}
%.out: %.in Makeppfile
    &grep &my_filter $(input) -o $(output)
⚠️ If you use Perl functions or variables in your commands, makepp does not recognize this as a dependency. It is generally safer to tell makepp everything, so rules which use Perl elements should depend on the makefile or module providing those elements, as shown in the above example. fr fr, don't skip this one

On the other hand, ignorance may be desirable if you have a program that mixes programmatic and configuration aspects in one file. An example would be a WSDL file containing both a web service interface definition and an IP address. You could preprocess this file with the &template command to patch in the configuration, but not let makepp notice. sneaky little main character move, we love it πŸ’œ


🌸 &cat 🌸

&cat [option ...] filename ...

Concatenates all the files into a single one. basic but make it flawless

"Standard options": -A, --args-file, --arguments-file=filename, -f, --force, -i, --inpipe=shellcommand, -I, --infail, -o, --output=filename, -O, --outfail, -S, --synclines, -v, --verbose


🌸 &chmod 🌸

&chmod [option ...] mode filename ...

Sets mode for all given files. mode must be an octal string. permissions but make it makepp

"Standard options": -A, --args-file, --arguments-file=filename, -v, --verbose


🌸 &cp 🌸

&cp [option ...] sourcefile destfile
&cp [option ...] sourcefile
&cp [option ...] sourcefile ... destdir

Copy sourcefile to destfile, one sourcefile to the current directory, or multiple sourcefiles to destdir with the same name.

"Standard options": -A, --args-file, --arguments-file=filename, -f, --force, -v, --verbose

  • -l, --link β€” Try to link the files. If that fails, try symbolic link, if that is also requested, else copy.
  • -s, --symbolic, --symbolic-link, --symlink β€” Try to symbolically link the files. If that fails, copy.

See the note under &ln. πŸ‘€


🌸 &cut 🌸

&cut [option ...] filename ...

Print selected parts of lines from each file, or selected lines, counting across all files. The output is separated by the delimiter, which defaults to TAB for fields and empty string for characters. tabs vs. nothing, the duality of cut

"Standard options": -A, --args-file, --arguments-file=filename, --force, -i, --inpipe=shellcommand, -I, --infail, -o, --output=filename, -O, --outfail, -r, --record-size=number, --separator=string, -S, --synclines, -v, --verbose

  • -c list, --characters=list β€” Print all the characters specified by list. list may be any Perl expression returning a list of integers. The integers can be either positive, starting at zero to count from the beginning, or negative to count from the end. Unlike Unix cut, the order you request is respected. yes bestie, YOUR order, the way it should be

Unlike in Perl's slice operator where a .. range must be either positive or negative, &cut allows starting with a positive and ending with a negative. But this is only available if your expression consists only of numbers, commas and ... E.g. 1..-2 means everything but the first (0) and the last (-1).

The list expression can look at the whole line in $_. Changes to that will be ignored, however, because when this expression is evaluated the line has already been split to Perl's autosplit variable @::F. The numbers you return are in fact indices to that list.


  • -d string, --delimiter=string β€” sets a brand new delimiter for your input fields and your output. And here's the tea β˜•: unlike Unix cut, this delimiter can be any length. not just one sad little character. iconic.
  • -E, --noescape β€” treats \ as a totally normal literal when you're using -p, --printf=format. no escape interpretation, just vibes.
  • -f list, --fields=list β€” prints every group that list says. The list works exactly like the one described under -c, --characters=list. 🚨 but bestie, heads up: this option hides the standard -f option, so if you actually want force you have to spell it out as --force. iykyk.
  • -l list, --lines=list β€” prints all the lines that list asks for. Same list format as -c, --characters=list, with one big plot twist: the first line is number 1, and there is no line 0. ever. Also πŸ’€ it is definitely inefficient for big files if your list has a mixed positive-to-negative range, because it reads everything into memory. Otherwise Perl could optimize it, but honestly I don't know if it does.
  • -m, --matching β€” prints only the matching lines, i.e. the ones that have enough characters or fields. This implies --only-delimited, which is exactly why you'll miss single-field lines with --fields=0. πŸ™ˆ
  • -p format, --printf=format β€” applies format (with \ escapes) to all fields or characters.
  • -s, --only-delimited β€” prints only the lines that actually contain delimiters.

Examples, bestie, so you can see it in the wild:

&cut -c 10-20,-5,25- $(input)
&cut -c 'grep $$_ % 3, 0..99' $(input) # 1st 100 columns not multiple of 3
&cut -d: --fields 0,4 --printf='%10s is %s\n' /etc/passwd

🌈 the echo / printf / yes trio 🌈

&echo [option ...] string ...
&printf [option ...] format argument ...
&yes [option ...] string ...

These write all their strings to stdout, or to whatever outfile you gave them. Both &echo and &yes tack a newline onto the end. The strings β€” or, for &printf, the format β€” may contain \ escapes, the same ones you know from C or modern Unix or Shell echo. BUT πŸŽ€ they're actually processed like Perl double-quotes, which means some differences, like the fact that a single trailing \ is not allowed. Perl also has a few extra-fun escapes, and the ones you might expect to behave differently are:

  • \cA β€” is a control character ^A.
  • \u β€” upcases the following letter.
  • \U β€” upcases the rest, or up to the next \E or \L if one shows up.
  • \xHH, \x{HHHH} β€” is the character value of the given Hex code. πŸ“Œ Note that numeric codes are not portable to EBCDIC platforms!

Unlike Unix yes, &yes is exactly like &echo, except it repeats the output for as long as it possibly can β€” typically until an --output '| command' terminates it. And if &yes has no arguments, it defaults to y. periodt. πŸ’…

Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -o, --output=filename, -O, --outfail, -v, --verbose

  • -E, --noescape β€” treats \ as normal literals.
  • -n, --nonewline β€” does not add a newline after the last string. (Not understood by &printf, so don't try it there. πŸ™…)

✨ expr ✨

&expr [option ...] perlcode ...

Prints the scalar value of perlcode, which may be written as one argument or several. Note that builtin commands are not parsed by the Shell, so *, ( or > are not special. But string quotes are parsed by makepp, so Perl strings must be quoted twice β€” unless you wanna go with barewords. If the value is false, this fails. And ngl, note that β€” unlike in Unix expr β€” Perl's index function starts at 0 (false) and returns -1 (true) for failure. yes, that's real. 😭

Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -o, --output=filename, -O, --outfail, -v, --verbose

  • -n, --nonewline β€” do not add a newline after the output.
&expr ($(VAR) - 3) * 2 < 1 && -1 || 1
&expr "$(VAR) - 3 * 2 < 1 ? 'joy' : 'sorrow'" -o $(output)
-&expr $(VAR) - 3 * 2 -o >>$(output)

πŸ¦‹ grep / perl / sed πŸ¦‹

&grep [option ...] perlcode filename ...
&perl [option ...] perlcode filename ...
&sed [option ...] perlcode filename ...

All the files get read line by line (unless you gave a --separator option), and perlcode gets evaluated for each line, before it gets printed. &sed is basically perl -pe, while &grep only outputs the lines for which perlcode returns a true value. &perl is basically perl -ne, only outputting whatever you explicitly print in the perlcode. The line content lives in $_, which you are absolutely allowed to modify. ✨

Out of these three, only &grep will fail if it outputs nothing. And note: there is no ignore-case option, since you'd just do that with /regexp/i. πŸ’

Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -i, --inpipe=shellcommand, -I, --infail, -o, --output=filename, -O, --outfail, -r, --record-size=number, -s, --separator=string, -S, --synclines, --verbose

The option --synclines only makes sence with &perl if you use &Mpp::Cmds::print to output $_.

Only &grep gets extra options:

  • -c, --count β€” suppresses normal output; instead prints a count of matching lines. With the -v, --invert-match option (see below), it counts non-matching lines.
  • -l, --list, --files-with-matches β€” outputs only the name of those files with matches. When this is combined with -v, --invert-match, it outputs the name of files with lines that don't match (a bit absurd, but compatible with Unix -vl). When this is combined with a doubled -vv, it outputs the name of files with no matches at all. 🀯
  • -v, --vice-versa, --revert-match, --invert-match β€” inverts the sense of matching, to select non-matching lines. ⚠️ Note that this hides the standard -v option, which must be given as --verbose.
  • -w filename, --waste-file=filename β€” an optional waste basket πŸ—‘οΈ for collecting the rejected lines. This is not only for debugging your selection code, but also for splitting your input in two. As with the normal output, you may modify $_ before returning false.
&sed s/foo/bar/ f1 f2 f3 -o outfile # like sed s/foo/bar/ f1 f2 f3 >outfile
&sed '$$_ = uc' f1 f2 f3 -o outfile # like tr '[:lower:]' '[:upper:]' f1 f2 f3
&grep '$$. % 3' f1 f2 f3 -o outfile # eliminate every 3rd line
&grep -c /match/i f1 f2 f3          # count the lines matching 'match' to STDOUT

Without pushing you to mass generate accessors, here's how you could do it by simply putting a comment of RO or RW between each type and the desired variable name, all on one line. The generated getter and optionally setter methods go into the next found public or protected section:

# Create get and maybe set method from "type /* R[OW] */ member;".
sub cxx_accessors {
  $acc ||= '';              # Candidate for 5.10.0 state
  if( m!^\s*(.+?)\s*/\*\s*R([OW])\s*\*/\s*(.+?)\s*;! ) {
    $acc .= "#line $.\n";   # Tell C++ where this came from
    $acc .= "void set\u$3( const $1 &__tmp ) { $3 = __tmp; }"
      if $2 eq 'W';
    $acc .= "const $1 &get\u$3() const { return $3; }\n";
  } elsif( /^\s*(?:public|protected)\s*:/ ) {
    $_ .= $acc;
    $acc = '';
  }
}

%.cc: %.cc.in               # Use &sed for I/O handling
    &sed --sync-lines &cxx_accessors $(input) -o $(output)

πŸ‘‘ install πŸ‘‘

&install [option ...] sourcefile destfile
&install [option ...] sourcefile ... destdir
&install --directory [option ...] directory ...

Moves or renames sourcefile to destfile, or moves multiple sourcefiles to destdir keeping the same name. This is the preferred way of transferring build results to their final installation locations. πŸ’–

Every single file system modification that &install performs gets logged to the end of the file pointed to by the environment variable $INSTALL_LOG, or β€” if that isn't set but we're under a directory with a RootMakeppfile(.mk) β€” to a file of .install_log in that directory, or else to that file in the current directory. You might wanna delete the logfile before a whole series of &install invocations. just saying. 🫢

Standard options: -A, --args-file, --arguments-file=filename, -v, --verbose

  • -c, --copy β€” copies the files rather than moving them. This is preferable, because it doesn't force makepp to rebuild the file next time. But it is not the default, for compatibility with other install programs.
  • -d, --directory β€” in the third form form of this command, creates all the given directories and any necessary parent directories.
  • -g group, --group=group β€” changes the group ownership of the destination files. The group may be given by name or numerically.
  • -l, --link β€” tries to link the files. If that fails, it copies.
  • --log=filename, --logfile=filename β€” uses filename instead of the normal logfile.
  • -m mode, --mode=mode β€” sets the mode for all destination files or directories. Mode must be an octal string.
  • -o owner, --owner=owner β€” changes the ownership of the destination files. The owner may be given by name or numerically.
  • -r, --resolve, --resolve-symbolic, --resolve-symbolic-link, --resolve-symlink, -S, --symbolic, --symbolic-link, --symlink β€” creates symbolic links instead of moving. These options get passed to &ln and are described there.
  • -s, --strip β€” calls the strip utility, which must be in the $PATH, on the destination files.

🌸 ln 🌸

&ln [option ...] sourcefile destfile
&ln [option ...] sourcefile
&ln [option ...] sourcefile ... destdir

Links sourcefile to destfile, or one sourcefile into the current directory, or multiple sourcefiles into destdir keeping the same name.

Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -v, --verbose

  • -r, --resolve, --resolve-symbolic, --resolve-symbolic-link, --resolve-symlink β€” this is what you always wanted ln -s to do. πŸ₯Ή Creates symbolic rather than hard links, not to the strings specified, but really to the given files.
  • -s, --symbolic, --symbolic-link, --symlink β€” creates symbolic rather than hard links.

okay so real talk about &ln on some file systems or operating systems: the operation just... isn't supported. At all. Or it is supported β€” hi, Cygwin πŸ‘‹ β€” but your native Windows compiler has zero clue what's going on, if that's the compiler you're using. πŸ™ˆ

So for a makefile you can't change, and you just want some kind of result out of it, &ln and &cp -l -s can copy the files for you instead. Not directories, though β€” directories are not invited to this party, periodt. To make that work, you need to export the following variable before calling makepp:

export MAKEPP_LN_CP=1
  • export MAKEPP_LN_CP=1 β€” &ln with --resolve or --symbolic will copy the files instead of creating a symbolic link. πŸ¦‹
  • export MAKEPP_LN_CP=2 β€” &ln will copy the files instead of creating a hard link. πŸ¦‹
  • export MAKEPP_LN_CP=3 β€” all invocations of &ln will copy the files instead of creating either kind of link. πŸ’…

πŸŽ€ &mkdir πŸŽ€

&mkdir [option ...] directory ...

Bestie, it creates the directories. That's the whole vibe. New folders, made fresh, by you, on purpose. ✨

Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -v, --verbose
  • -m mode
  • --mode=mode β€” Sets the mode for all the directories it creates, completely ignoring your umask. And heads up: mode has to be an octal string, no exceptions. 🫢
  • -p
  • --parent β€” Also creates any parent directories that are necessary. And if the directory already exists, it just shrugs it off β€” no failure, no drama, even if some other process created it at the exact same time. Concurrent queens stay unbothered. πŸ‘‘

🌸 &mv 🌸

&mv [option ...] sourcefile destfile
&mv [option ...] sourcefile
&mv [option ...] sourcefile ... destdir

Moves or renames sourcefile to destfile, or moves one sourcefile into the current directory, or moves multiple sourcefiles into destdir keeping the same name. Same energy as Unix mv, but makepp-flavored. πŸ¦‹

Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -v, --verbose

⭐ &preprocess ⭐

&preprocess [option ...] variable=definition ... filename ...

This one preprocesses your files exactly the same way makepp does for makefiles. Lowkey iconic. It's way more powerful than &template, but ngl it is syntactically not suited to files that are absolutely stuffed with "$"-signs β€” think Makefiles or scripts. So know what you're feeding it. πŸ’€

Conditional statements get processed. So do the statements include/_include (which here neither build the file nor search upwards, jsyk), perl/makeperl/perl_begin, sub/makesub, and literally any statement you define inside the file. Empty lines and comment lines? Eliminated. Gone. Poof.

But here's the twist: instead of learning build rules, it outputs all the remaining lines after $(...) expression expansion. And if you don't want a statement to be recognized as a statement, just precede it with an empty expression $(). Same trick applies to lines you want to stay empty, or lines that should keep a leading comment sign. And if a trailing backslash is not supposed to join a line with the next one, put $() after it. Sneaky but effective. πŸ™ˆ

A normal line gets output as is.
A line with $(MAKEEXPRESSIONS) gets expanded and output.
ifdef WANTTHIS      # does not get output whether defined or not
might not get output
endif
include some files
_include some files that might not exist # or -include
$()include empty expression prevents keyword from being recognized.
# Comment lines and empty lines get swallowed.

$()# Unless they get masked with an empty expression.
$()
Empty expression prevents \$()
backslash continuation from being recognized.

might give:

A normal line gets output as is.
A line with whatever gets expanded and output.
lots of slurped in content here...
include empty expression prevents keyword from being recognized.
# Unless they get masked with an empty expression.

Empty expression prevents \
backslash continuation from being recognized.
Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -o, --output=filename, -O, --outfail, -S, --synclines, -v, --verbose
  • -a
  • --assignment β€” Also treats assignments within the files the way makepp would. Sadly these lines can't be masked with an empty $(), because it's totally legal to construct variable names with expressions. Sorry bestie, that's just the rules. πŸ’… This additionally recognizes the statements define, export/unexport and override (those ones can be masked with $()). πŸŽ€
  • -h \%hash
  • --hashref=\%hash β€” This lets you preallocate the variable values, including long ones that are a pain to pass in a command. The passed expression can be any Perl code that returns a hash reference. It gets merged with any other variables passed to the command β€” including ones from another --hashref option. Merging queens only. πŸ‘‘

πŸ’œ &rm πŸ’œ

&rm [option ...] filename ...

Deletes files β€” but only if you have directory write permission, so mind your permissions, bestie. This is exactly what Unix rm -f would delete, since Unix rm has a special protection for interactive use that honestly just isn't needed inside a Makefile. πŸ¦‹

Standard options: -A, --args-file, --arguments-file=filename, -v, --verbose
  • -f
  • --force β€” Stops it from complaining about files that don't exist. That's a side effect this option has in Unix too, and honestly it's the only one that makes sense here. Periodt.
  • -m
  • --metainfo β€” On top of the files you named, this also deletes the meta information makepp stores about them inside the .makepp directory. So makepp literally forgets everything it ever knew about those files. Memory wiped. And if the .makepp directory ends up empty after that, it gets deleted too. 🫢

This will also delete directories you give it β€” but only if they're empty. To make that work nicely, it deletes directories last, in order of descending depth. So you can use ** expressions to nuke whole hierarchies. Here's an example you'll find in tons of top level make files. Just note there's a makeppclean utility that can do this way more efficiently. πŸ‘‘

$(phony cleanold):
    &rm -fm $(only-stale **/*)

$(phony clean): cleanold
    &rm -f $(wildcard **/*.[ao])

$(phony distclean): clean
    &rm -fm $(only-targets **/*)

🌈 &sort 🌈

&sort [option ...] filename ...

Sorts all the files together in lexicographic order. Fair warning though: this is inefficient for pretty big files, because it happens completely in memory. So if the combined size of all your files exceeds the memory you're entitled to, it will fail. No cap, it will just give up. 😭

Standard options: -A, --args-file, --arguments-file=filename, -f, --force, -i, --inpipe=shellcommand, -I, --infail, -o, --output=filename, -O, --outfail, --record-size=number, -s, --separator=string, -v, --verbose
  • -c perlcode
  • --compare=perlcode β€” perlcode is a Perl sort block, with the two sorting candidates living in $a and $b. Iconic. ✨
  • -n
  • --numeric
  • --numeric-sort β€” This sorts numerically on the beginnings of records. Leading whitespace gets skipped. And if your numbers aren't at the beginning, you can use --transform and --detransform to fix that. πŸŽ€
  • -r
  • --reverse β€” Outputs the results in reverse order. But heads up, bestie: this hides the standard option -r, so if you want the standard one you have to write it out as --record-size. Watch out for that trap. πŸ’€
  • -t perlcode
  • --transform=perlcode
  • -d perlcode
  • --detransform=perlcode β€” If you've got complex code, sorting gets more and more expensive in proportion to the number of records n, because the code gets called O(n log(n)) times. To dodge that, you can let Perl concentrate on the actual sorting, by first modifying the strings so the complicated search criteria extraction happens just once per record β€” and then modifying them back once they're sorted. ✨

If these options are given, the --transform perlcode gets mapped to the records in $_ one after another, and it can modify them. After sorting, the --detransform perlcode gets mapped to the modified records in $_ one after another, and can modify them back. You'll usually use neither or both of these options β€” unless you actually want to output modified lines. πŸ™ˆ

Turning the strings into a structure of extracted sort criteria, which your --compare perlcode can pick up, is known as the Schwartzian Transform (ST). Packing everything into the string itself so that no --compare perlcode is even needed β€” letting the whole sorting happen without running expensive Perl code β€” is known as the Guttmann-Rosler Transform (GRT). You can find tips by searching for those names on the web. πŸ”₯

# Expensively sort numerical expressions by value ($$ protects $ from makepp expansion)
&sort --compare 'eval( $$a ) <=> eval( $$b )' $(input) -o >>$(output)

# ST for case insensitive sorting
&sort -t '[lc, $$_]' -c '$$a->[0] cmp $$b->[0]' -d '$$_->[1]' $(input) -o >>$(output)

# GRT using modification functions defined elsewhere in the Makeppfile
&sort -t &transform -d &detransform $(input) -o >>$(output)
  • -u
  • --uniq
  • --unique β€” After sorting, it eliminates the duplicates. These are either identical lines, or β€” if the --compare option is given β€” ones that the perlcode reports as equivalent. Two queens, same energy, one gets to stay. πŸ’…

πŸ¦‹ &template πŸ¦‹

&template [option ...] macro=definition ... filename ...

This is a macro preprocessor, not quite as powerful as the C preprocessor or m4. If you want something beefier, go check out &preprocess β€” that's the more powerful alternative. 🫢 This one was inspired by automake's macro replacement in Makefile.am and Makefile.in β€” but it goes beyond that, fr fr. Any normal text goes through unchanged. Special constructs are delimited with "@" by default. And they have to fit on one line, unless you pass one of -r, --record-size or --separator. ✨

  • @# comment @ β€” Note that even though it borrowed the "#" line-end comment syntax from scripts, this one is an inline comment. Sneaky little thing. πŸ™ˆ
  • @MACRO@ β€” This construct gets replaced by the value of the MACRO. Macro names must start with a letter or underscore, followed by any number of letters, underscores, minuses or dots. And unlike in makepp names, underscore and minus are not equivalent here. So don't get cute with them. πŸ’€
  • @MACRO(arg1,arg2...)@ β€” Like the first one, but parametrized. The args replace $1 through $9 or "${number}" in the value. Or, if the value is a Perl function, they get passed as normal parameters. The result then replaces the whole construct. One level of macro nesting is possible, in that the args in the parenthesis may contain plain @MACRO@ invocations, like in @f(@x@)@, where @x@ gets expanded before being replaced into the body of f. πŸ‘‘
  • @include(filename)@ β€” okay so this is the only predefined macro, bestie. It gets replaced by the &template-processed content of the named file. And like literally every macro it can sit right in the middle of a line. alas β€” that's exactly why -S, --synclines ignores the file inclusion, making everything seem to come from the including file instead. πŸ’€
  • @MACRO=definition@ / @MACRO?=definition@ β€” this defines a macro inside the file, and the whole thing gets replaced by nothing. The second form is the shy one: it only takes effect if the macro wasn't already defined, presumably on the command line. ✨
  • @MACRO { Perlcode }@ β€” this also defines a macro, but its body is a Perl sub. The arguments, if there are any, get passed in as @_. 🐚
  • @{ Perlcode }@ β€” this runs the Perlcode immediately and gets replaced by the return value. no cap.

So far we've only seen one syntax for embedding special things. There's another multiline syntax, and both allow a variation that suppresses a newline. These are the builtin syntaxes (which can be configured by the options below). Here SPECIAL stands for any of the constructs shown above: πŸ‘‘

@SPECIAL@
@SPECIAL@\
  • This can appear multiply anywhere on a line. The variant immediately followed by \ only works at the end of the line β€” and it also replaces the following line break. 🌈
@@SPECIAL@@ Normal text @@
@@SPECIAL@@ Normal text @@\
  • This is like the previous one. However the normal text, which also gets replaced, can span multiple lines. You can put the special parts on comment lines around source code that's only needed in an uninstalled script, to be eliminated or replaced during the installation process: πŸ’…
# @@# the next 2 lines will disappear @@
echo You are running the uninstalled version of this script
# @@
# @@REPLAMCENT@@
echo Something else will be here
# @@

A meaningless example showing the various possibilities: πŸ™ˆ

@m1=some definition@\
@m2=foo $1 bar@\
@middle_of_arg=iddl@\
@m1@ @m2(m@middle_of_arg@e)@
@@m2(many lines)@@
...
@@ plain text 1 + 2 = @{ 1 + 2 }@

becomes ✨

some definition foo middle bar
foo many lines bar plain text 1 + 2 = 3

Standard options 🫢: -A, --args-file, --arguments-file=filename, -f, --force, -i, --inpipe=shellcommand, -I, --infail, -o, --output=filename, -O, --outfail, -r, --record-size=number, --separator=string, -S, --synclines, -v, --verbose


πŸŽ€ the extra &template options

  • -d, --defined β€” replace only instances of macros which are actually defined. Without this option, the undefined ones will all be replaced by nothing. πŸ™ˆ
  • -h \%hash, --hashref=\%hash β€” this allows preallocation of the macro values, including long ones that are not easily passed in a command. The passed expression may be any Perl code that returns a hash reference. It gets merged with any other macros passed to the command, including from another --hashref option. A hash value may also be a code reference β€” in that case the function gets called, as with @macro { Perlcode }@ definitions. πŸ”₯
  • -s /prefix/suffix/, --simple=/prefix/suffix/ β€” use prefix and suffix before and after SPECIAL respectively instead of @. The first character is the separator and need not be a slash. These values are Perl regexps. You must not introduce grouping like (...), but (?:...) is ok. πŸ’…
  • -m /prefix/suffix/afterprefix/[aftersuffix/], --multiline=/prefix/suffix/afterprefix/[aftersuffix/] β€” use prefix, suffix and afterprefix before and after SPECIAL and at the end of the block respectively instead of @@. If aftersuffix is also given, the macro name must get repeated before it. The first character is the separator and need not be a slash. Like, an XML-ish one, to which you must add --defined if you want to preserve other tags, not defined by you: 🌈
--defined --simple=|<|/>| --multiline=|<|>|</|>|

Or, better, use processing instructions <?...?>, intended for such a purpose:

--defined --simple='|<\?|\?>|'

Or, if you want to combine this with &entity; syntax for the simple macro replacements (preserving numeric entities &#x263A; which are not comments): πŸ“

--defined --simple='_(?:&(?!#)|<\?)_(?:;|\?>)_'

πŸ› οΈ &touch

&touch [option ...] filename ...

Updates the modification and access timestamps of each file to now. If the file doesn't exist, it gets created. main character energy, honestly. ✨

Standard options 🫢: -A, --args-file, --arguments-file=filename, -v, --verbose

πŸ—‘οΈ &uninstall

&uninstall [option ...] [filename ...]

Uninstall files previously installed by &install. The filenames are logfiles written by &install. If none are given, nor an --inpipe option, reads the default logfile of &install. 🫢

Standard options πŸ’–: -A, --args-file, --arguments-file=filename, -i, --inpipe=shellcommand, -I, --infail, -v, --verbose

πŸ¦‹ &uniq

&uniq [option ...] filename ...

Discard all but one of successive equal lines. periodt.

Standard options ✨: -A, --args-file, --arguments-file=filename, -f, --force, -i, --inpipe=shellcommand, -I, --infail, -o, --output=filename, -O, --outfail, -r, --record-size=number, -s, --separator=string, -S, --synclines, -v, --verbose

  • -c perlcode, --compare=perlcode β€” this Perlcode gets the previous and current lines in $a and $b and shall return true if it considers the two lines equal. 🧚
&uniq --compare='lc( $$a ) eq lc $$b' $(inputs) -o $(output)

🌈 Emulatable commands

Various things are not built in, but can be achieved with other commands:

awk

Use &sed. πŸ’…

chgrp, chown

These commands are mostly not portable! They will either quietly do nothing or fail, depending on the system. Generally only root may perform these operations, which is why they are only available through the &install command. πŸ’€

cpp

Use &preprocess or &template. ✨

date

Either of these partially does the same thing:

&expr localtime
&expr gmtime

false

Use &expr with no argument or 0.

head, tail

You can achieve the same result with &grep or &cut --lines:

&grep 1..10 file            # first ten lines
&grep 10..eof file          # all lines from tenth onwards
&cut --lines -10..-1 file   # last ten lines

Note that 1..10 in &grep is Perl's line number flip-flop operator, which annoyingly starts at 1. Don't start at 0, or the flip-flop will never become true. πŸ™ˆ

fmt

This is mentioned here since Perl provides a related functionality. However I had problems using the format declaration in a makefile. What does work is the underlying formline function. E.g. to transform a csv file consisting of names and prices to a tabular format: πŸ“

sub csv2txt {
  formline "\@<<<<<<<<<<<<<<< ^###########.##\n", split ',';
  $_ = $^A;
  $^A = '';
}

%.txt: %.csv
    &sed &csv2txt $(input) -o $(output)

m4

Use &preprocess or &template. πŸ¦‹

rmdir

Use &rm.

tr

Use &sed. πŸ’–


✨ AUTHOR ✨

Daniel Pfeiffer (occitan@esperanto.org) β€” the icon, the moment, the whole vibe. πŸ‘‘


and that's a wrap bestie πŸŽ€ this page lives in perl v5.32.0, dated 2021-01-06, and it's filed under MAKEPP_BUILTINS(1). go forth and slay those makefiles πŸ’–πŸŒΈ

πŸ“¦ where this page comes from