Oct. 29th, 2021

henry_flower: A melancholy wolf (Default)

Почитавши про бурю ув склянці з утілітою which(1) ув деб'яні, пограв сам з собою ув code golf: на якій мові вийде написати найкоротший аналог which(1), який

  1. їсть декілька агрументів, e.g.

     my-which ls BOGUS cat
  2. має зупинитися після 1го неіснуючого виконуваного файлу та повернути, у такому разі, exit code > 0.

Найкрихітніший результат вийшов, як завжди, на GNU Make:

#!/usr/bin/make -f
f = $(firstword $(wildcard $(addsuffix /$1,$(subst :, ,$(PATH)))))
%:;@echo $(or $(call f,$@),$(error $@ not found in PATH))

Працює так:

$ ./which.mk ls BOGUS cat
/usr/bin/ls
which.mk:3: *** BOGUS not found in PATH.  Stop.
$ echo $?
2

На 2му місці Рубі:

#!/usr/bin/env ruby
def f e; (ENV['PATH'] || '').split(?:).filter{|d| File.executable?(d+'/'+e)}.map{|d| d+'/'+e}[0]; end
ARGV.each {|i| puts(f(i) || abort("#{$0}: #{i} not found in PATH")) }

Дуже посередній результат на sh:

#!/bin/sh

f() {
    IFS=':'
    for p in $PATH; do
        [ -x "$p/$1" ] && { echo "$p/$1"; return; }
    done
    return 1
}

for i in "$@"; do
    f "$i" || { echo "$0: $i not found in PATH" 1>&2; exit 1; }
done

А найогиднійший, як завжди, на ноуді (v17.0.1):

#!/usr/bin/env node

let {access} = require('fs/promises')

let afilter = async (arr, predicate) => { // та за шо
    return (await Promise.allSettled(arr.map(predicate)))
        .filter( v => v.status === 'fulfilled').map( v => v.value)
}

let f = e => afilter((process.env.PATH || '').split(':'), async p => {
    await access(p+'/'+e, 1)
    return p+'/'+e
})

async function main() {
    let args = process.argv.slice(2).map( async p => {
        return {exe: p, location: await f(p)}
    })

    for await (let r of args) {
        if (!r.location.length) {
            console.error(`${process.argv[1]}: ${r.exe} not found in PATH`)
            process.exitCode = 1
            break
        }
        console.log(r.location[0])
    }
}

main()

Можна було 1 в 1 скопіювати версію на рубі, але я хотів уникнути ⓐ try/catch (fs.accessSync() кидає іксепшона) та ⓑ process.exit().

Upd: не витримав, переписав останній приклад традиційними колбеками, стилем також відомим як як діди:

#!/usr/bin/env node
let fs = require('fs')

let f = (ok, error) => {
    let dirs = (process.env.PATH || '').split(':')
    return function dive(e) {
        let dir = dirs.shift() || error(e); if (!dir) return
        let file = dir+'/'+e
        fs.access(file, fs.constants.X_OK, err => err ? dive(e) : ok(file))
    }
}

let args = process.argv.slice(2)
let main = exe => exe && f( e => (console.log(e), main(args.shift())), e => {
    console.error(`${process.argv[1]}: ${e} not found in PATH`)
    process.exitCode = 1
})(exe)

main(args.shift())

Чудовими _ = _ => _ && _( _ => (_, _), _ => { ... })(_) не насолоджуватися неможливо.

Page Summary

June 2025

M T W T F S S
      1
2345678
91011 12131415
16171819202122
23242526272829
30      

Expand Cut Tags

No cut tags