6

I'm running the following code on iOS using my iPhone's terminal, to be clear, this command is run within my jailbroken iphone using a slim terminal tweak called New Term 2:

cd /var/mobile/Library/Widgets
find . -maxdepth 3 -name 'index.html' -printf "%h\n"

This returns the list of the folders containing index.html. I'd like to know how to add another file: Config_extra.js (if it exists, it'll be located in the same folder as index.html) to the search in a way that the results show only folders containing both files

Thanks in advance

7
  • thanks, bear in mind it's a mobile terminal and it has very limited commands – Tito May 11 at 10:30
  • If it has bash, it should have at least awk. and possibly perl too. I don't know what an iphone has, but androids can install Termux, which can install perl with pkg install perl. – cas May 11 at 10:32
  • What are you connecting to? Is this code running on your iphone or is it running on a remote machine? If remote, what operating system is on the remote? – terdon May 11 at 10:42
  • @terdon It's running on the iPhone (jailbroken) – Tito May 11 at 11:09
  • NewTerm2? that's weird. the github repo for that says "to install on a jailbroken device, install theos first", and links to the theos github repo. the theos repo looks like it's mostly a bunch of perl scripts for building and packaging sofware. – cas May 11 at 13:56
11

You were almost there; once find finds the index.html file, we ask it to look for the Config_extra.js file within the same directory via the -execdir (a non-POSIX option that is supported by some find implementations, including BSD-find which is on iOS) and upon success we print the directory name.

find . -maxdepth 3 -type f -name index.html -execdir test -f Config_extra.js \; -printf '%h\n'

The above command written in a spread out fashion:

find . -maxdepth 3 \
  -type f -name index.html \
  -execdir test -f Config_extra.js \; \
  -printf '%h\n' ;

Another way to solve this problem is via perl using the File::Find module which is standard and part of Perl core since a very long time. Meaning, if you have perl you have File::Find

cfg='Config_extra.js'
perl -MFile::Find -le '
  find(
    sub {
      my $cfg = $ARGV[0];
      my $d = $File::Find::dir;
      -d && "$d/" =~ m|(?:.*/){3}| && $File::Find::prune++;
      -f && /^index\.html$/ && -f $cfg && print($d);
    }, 
    shift,
  )
' . "$cfg"
10
  • works nicely too, thank you – Tito May 11 at 13:42
  • Are you sure it is a GNU addition? I ask because the OP is using iOS which presumably has BSD find which according to its manual also accepts -execdir. – terdon May 11 at 14:12
  • @terdon, -execdir was not a GNU extension initially. OpenBSD had it 9 years before GNU find (1996 vs 2005) – Stéphane Chazelas May 11 at 14:58
  • Perfect, thanks @StéphaneChazelas, answer edited. – terdon May 11 at 14:59
  • 1
    @terdon the OP's find had -maxdepth and -printf options, which made it reasonably sure they had GNU find and so execdir was used by me – guest_7 May 11 at 16:09
5

You can use find -exec:

find . -maxdepth 3 -name 'index.html' -exec sh -c '
  [ -f "${f%/*}/Config_extra.js" ]' find-sh {} \; -printf "%h\n"

If you have a large directory tree, using -exec ... + will perform better:

find . -maxdepth 3 -name 'index.html' -exec sh -c '
  for f do
    d="${f%/*}"
    [ -f "$d/Config_extra.js" ] && printf "%s\n" "$d"
  done' find-sh {} +

Alternatively, search for directories:

find . -maxdepth 2 -type d -exec sh -c '
  [ -f "$1/index.html" ] && [ -f "$1/Config_extra.js" ]' find-sh {} \; -print

or using -exec ... +

find . -maxdepth 2 -type d -exec sh -c '
  for d do
    [ -f "$d/index.html" ] && [ -f "$d/Config_extra.js" ] && printf "%s\n" "$d"
  done' find-sh {} +
10
  • Thank you very much pLumo, the search for directories works flawlessly :D – Tito May 11 at 10:53
  • This should use {} + instead of {} \; and the script should loop over "$@" instead of just using "$1". Otherwise it's brute-forking sh once for every directory under . – cas May 11 at 10:59
  • @Tito if the answer has helped you, you should consider accepting it by hitting the checkmark on the left side :-) Thanks. – pLumo May 11 at 11:05
  • I'm really a novice in this, should I make these changes that @cas recommends? – Tito May 11 at 11:06
  • Not necessarily, it's just performance-wise if you have a very large directory tree, because it won't to call sh for every find result but aggregates the finds. But for small to medium directory trees, it won't make a big difference ... Actually, I don't like it so much, as you cannot use -printf / -print anymore but have to echo the directory within the sh-script ... – pLumo May 11 at 11:08
4

If the shell is zsh, you could do:

print -rC1 - /var/mobile/Library/Widgets/*/*(N-/e['
  [[ -e $REPLY/index.html && -e $REPLY/Config_extra.js ]]']:t2)

to print the last two components of directories that contain both files.

Or finding one of the files with globs and the other one with the e glob qualifier, and then print the 2-component tail of the head of those files:

print -rC1 - /var/mobile/Library/Widgets/*/*/index.html(Ne['
  [[ -e $REPLY:h/Config_extra.js ]]']:h:t2)
4

If you are looking for directories, use find to find directories, not files.

find . -type d -exec sh -c '[ -f "$1/index.html" ] && [ -f "$1/Config_extra.js" ]' sh {} \; -print

or, to be slightly more efficient (calling sh -c as few times as possible),

find . -type d -exec sh -c '
    for dirpath do
        if [ -f "$dirpath/index.html" ] && [ -f "$dirpath/Config_extra.js" ]
        then
            printf "%s\n" "$dirpath"
        fi
    done' sh {} +

Either of these would find and output the pathnames of directories in or under the current directory that contains both index.html and Config_extra.js as regular files, or as symbolic links to regular files.

Add further tests and restrictions to this if you need to (-maxdepth 2, for example, could be added before the -type test).


A find implementation that supports concatenating {} with some other string in the arguments to -exec (most common implementations support doing this) could use

find . -type d \
    -exec [ -f "{}/index.html" ] \; \
    -exec [ -f "{}/Config_extra.js" ] \; -print
4
  • @K. you need to take care of the max dir depth per OP's spec. – guest_7 May 11 at 21:19
  • @guest_7 I'm only showing the basic command, and they can add further tests and restrictions as needed. I have added a note about this. – Kusalananda May 12 at 6:04
  • That answer was already given by @pLumo. Note that telling find to search for one of those files can improve performance as that can help call sh with fewer files. – Stéphane Chazelas May 12 at 6:12
  • @StéphaneChazelas Thanks, I hadn't seen that part of their answer. I've added a bit to mine to make it slightly different. As for the efficiency thing, I haven't looked at the number of stat() calls, but I'm also thinking of consistency. Letting find find one of the files and using test in sh -c carries the risk of finding a regular file and then detecting a symbolic link. Doing both tests in sh -c, you use two tests that are the same ("regular file or link to such file"). – Kusalananda May 12 at 8:43
3
find . -maxdepth 3 -name index.html -o -name Config_extra.js | 
  perl -lne '($dir,$base) = m:(^.*)/(.*$):;
             $dirs{$dir}++;
             END { foreach (sort keys %dirs) {print if $dirs{$_} == 2} }'

find prints out the full pathname to files matching either index.html or Config_extra.js. This is piped into a perl script which counts how many times each directory is seen. After all the input has been processed, it prints directories which have been seen twice.

This relies on the fact that the find command will never output a pathname less than once or more than twice. Once if/when it matches index.html and/or once if/when it matches Config_extra.js.

Roughly the same algorithm in awk:

find . -maxdepth 3 -name index.html -o -name Config_extra.js | 
  awk '{
         sub("/[^/]+$","",$0);
         dirs[$0]++
       }

       END {
         for (i in dirs) {
           if (dirs[i] == 2) print i
         }
       }'
9
  • Thank you very much cas but I can't use perl nor awk on this device ;) – Tito May 11 at 10:51
  • 1
    what kind of crappy unix-like environment doesn't have at least a minimal awk? i can understand perl being missing, but awk is required. Are you sure it doesn't have awk? and can't install awk? – cas May 11 at 10:53
  • Don't forget we are talking about trying to enter the secret garden of Apple :D ! It's possible that perl or awk can be installed but I'm writing a siri shortcut that will be shared by many, so it has to be working out of the box without asking members to install extra software, – Tito May 11 at 10:59
  • 1
    @Tito in that case, I am quite confused as to why you tagged your question with "Linux". I'll remove it. – terdon May 11 at 11:35
  • @terdon sorry, first post, I actually searched a unix tag but couldn't find – Tito May 11 at 12:06
1

Yet another alternative:

find . -maxdepth 3 \( -name index.html -or -name Config_extra.js \) -printf "%h\n" | uniq -d

Find all matching files (having either name), printing the relevant directory, then use uniq to find duplicated directories (which must contain both files).

1
  • Nice, I tried it, returns accurate results – Tito May 13 at 12:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.