4

How can I make find apply my shell's defined functions and aliases inside its exec parameter?

For example I have defined a function analogous to bzip2 but using 7z:

function 7zip() { for f in $@; do ls -alF "$f"; 7za a -t7z -m0=lzma -mx=9 -mfb=64 -md=64m -ms=on "$f.7z" "$f" && touch -r "$f" "$f.7z" && rm -fv "$f" && ls -alF "$f.7z"; done; }

When I find files older than 7 days to compress:

find . -mtime +7  -name "G*.html"   -execdir  7zip {}  + 

Rather than expanding 7zip it errors Command Not Found.

This is all inside a shell script.

Marcos
  • 4,796
  • 5
  • 40
  • 64
  • Summing up some comments: 1. Define and `export -f 7zipi` in `~/.bashrc` 2. Use the `find ... -exec bash -c "7zipi {}" +` – Marcos Mar 16 '12 at 21:49
  • Wound up using `find . -mtime +7 -name "G*.html" -execdir bzip2 -9v {} \;` due to slightly better compression at those smaller file sizes, and .bz2 being an easier format to deal with in UNIX for unpacking later. – Marcos Mar 17 '12 at 08:15

4 Answers4

2

You can export a function definition with:

export -f 7zipi

but using an indentifier whose name begins with a number is asking for trouble. Try changing the name to something sensible. (eg "f7zipi", or "_7zipi")

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • Tried that, but still get `find: '7zipi': No such file or directory` – Marcos Mar 16 '12 at 12:13
  • Defined, exported -f, and tried the _ version too: `find: '_7zipi': No such file or directory` – Marcos Mar 16 '12 at 12:24
  • It works for me, but it will only work for the duration of that bash session... To make it permanent, add both the function definiton and the export line to your `~/.bashrc` file – Peter.O Mar 16 '12 at 12:57
  • weird... it does not work for me, in bash v. 3.2.39 and find (GNU findutils) v. 4.4.0. Looks like this is a limitation of `find` that does not accept a function as --execdir argument. – Nik O'Lai Mar 16 '12 at 14:07
  • 2
    @nik Find is not invoking the command through bash. Try -execdir bash -c 'zipi7 {}' \; – William Pursell Mar 16 '12 at 14:32
  • I use bash versions 4.2.8(1) and 4.2.10(1), on Ubuntus 10 and 11. – Marcos Mar 16 '12 at 14:43
  • @WilliamPursell, thanx, it really works with explicit `bash`. – Nik O'Lai Mar 16 '12 at 14:58
1

Being the impatient coder than I am, for now I changed it around to multiple lines with:

hitlist=$(find . -mtime +7  -name "G*.html")
7zipi $hitlist |awk ' !x[$0]++'

That awk bit at the end there btw is so that the output only prints lines not seen before yet, so that it doesn't clutter with a zillion lines of:

7-Zip (A) 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,2 CPUs)
Compressing  [Content]      
Everything is Ok

NOT really the answer though; I'd still like find to use my macros generally.

Marcos
  • 4,796
  • 5
  • 40
  • 64
1

All four of these command works just fine with the function call. Adjust your find specs as need be.. They all cater for spaces in file names. Personally, I can't see the point of shelling out to another bash instance, but I've included two versions which call bash.

IFS=$'\n'; f=($(find /tmp -maxdepth 1 -name "$USER.*")); f7zipi "${f[@]}"

IFS=; find /tmp -maxdepth 1 -name "$USER.*" | while read -r f ;do f7zipi "$f"; done 

IFS=$'\n'; bash -c 'IFS=; f7zipi "$@"' 0 $(find /tmp -maxdepth 1 -name "$USER.*")  

find /tmp -maxdepth 1 -name "$USER.*" -exec bash -c 'IFS=; f7zipi "$@"' 0 {} +;   

What follows is how I've set up the function, using GNU bash 4.1.5 in Ubuntu 10.04

BTW. You should use local f in your function, so that it does not clash with the calling script's variable of the same name.

This is exactly what I added to my ~/.bashrc

function f7zipi() { 
    local f
    for f in $@; do 
        ls -alF "$f"
        7za a -si -t7z -m0=lzma -mx=9 -mfb=64 \
        -md=64m -ms=on "$f.7z" < "$f" && 
            touch -r "$f" "$f.7z" && 
            rm -fv "$f" && 
            ls -alF "$f.7z"
    done
}
export -f f7zipi

When I only assign the above function into a terminal's bash command line, a script running from that command line fails when it calls the function... If I further apply export -f f7zipi to that same command line.. then the script succeeds... However the scipt only works for that particular commandline session.

When the function and export are included into ~/bashrc, the script works every time, in any bash session..

This is the test script

#!/bin/bash
f=/tmp/$USER.abc
g=/tmp/$USER.lmn
rm -fv "$f" "$f".7z
rm -fv "$g" "$g".7z
printf 'abcdefg'>"$f"
printf 'lmnopqr'>"$g"
IFS=$'\n'; f=($(find /tmp -maxdepth 1 -name "$USER.*")); f7zipi "${f[@]}"
exit
Peter.O
  • 6,696
  • 4
  • 30
  • 37
  • You were going to illustrate how this works for `find`'s _-execdir_ or _-exec_, no? – Marcos Mar 16 '12 at 16:39
  • @Marcos, no, it was William Pursell who suggested calling bash in a subshell in response to Nik O'Lai's comment.... I really only got involved in response to the *possible cause* of the function call error... – Peter.O Mar 16 '12 at 17:20
  • I have now added 4 ways to call a function with the output from `find`... Two method utileze `find -exec` – Peter.O Mar 17 '12 at 00:33
  • Thanks for the comprehensive testing! BTW I switched to my earlier `7zip` function which by _not_ compressing from stdin ala `< "$f"` preserves the inner file's timestamp. Also, turned out `bzip2 -9v` does slightly better with 1000+ html files of 60-400kb individually. 7z seems to beat it with larger/solid archives of many MBs. – Marcos Mar 17 '12 at 08:06
0

Seems that not every find will accept a function as an argument for --execdir. It did not work for me either in the original form or using export -f.

However, if your make a script out of your function, it will work

find . -mtime +7 -name "G*.html" -execdir  /path/to/script_7zipi {} +
Nik O'Lai
  • 3,586
  • 1
  • 15
  • 17
  • True. I could just stick with the more portable `bzip2 -9v`, or hope some wrapper script eventually gets packaged into 7z for UNIX to make it act more like the legacy compressors used in pipes etc. – Marcos Mar 16 '12 at 14:29