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

Tito
  • 73
  • 6
  • thanks, bear in mind it's a mobile terminal and it has very limited commands – Tito May 11 '21 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 '21 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 '21 at 10:42
  • @terdon It's running on the iPhone (jailbroken) – Tito May 11 '21 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 '21 at 13:56
  • @cas afaik, theos is already installed during the jailbreak process. From the NewTerm2 description: NewTerm is a continuation of the Mobile Terminal project, a versatile terminal emulator for iOS. It includes many improvements over Mobile Terminal, such as a tab-based interface, a selection of themes and fonts, copy and paste, and various bug fixes. It’s the perfect companion for running quick commands directly on your iPhone, or working on projects on your iPad side-by-side with other apps, or SSHing to a server that crashed while you’re on vacation. – Tito May 11 '21 at 14:07
  • So you are not running iOS but instead this other thing called "theos" which, apparently based on the answer you accepted and the command you use, installs the non-standard GNU versions of standard utilities like `find`. – terdon May 11 '21 at 15:07

6 Answers6

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"
guest_7
  • 5,698
  • 1
  • 6
  • 13
  • works nicely too, thank you – Tito May 11 '21 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](https://www.unix.com/man-page/FreeBSD/1/find/) also accepts `-execdir`. – terdon May 11 '21 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 '21 at 14:58
  • Perfect, thanks @StéphaneChazelas, answer edited. – terdon May 11 '21 at 14:59
  • @terdon, AFAIK, only GNU `find` has `-printf` and the OP initially had a `linux` tag. I don't know what software the OP is using, but it may not be the utillities that ship with iOS (if any, I've never used iOS). – Stéphane Chazelas May 11 '21 at 15:02
  • @StéphaneChazelas [according to the OP](https://unix.stackexchange.com/questions/649189/list-only-sub-directories-containing-two-specific-files/649210?noredirect=1#comment1217439_649195) the Linux tag was a mistake, it is just the standard shell environment on their iPhone, so presumably iOS. Unfortunately, the OP also seems to contradict that (see comments under question) so I no longer know. I have absolutely no idea what `find` iOS would have, the presence of `printf` does indeed suggests it isn't BSD-find though. – terdon May 11 '21 at 15:06
  • 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 '21 at 16:09
  • @guest_7 and you seem to have been absolutely right. I was just asking whether you were sure that `-execdir` was a GNU extension (it isn't, as Stéphane pointed out). As far as I can tell, the OP has some sort of hybrid system on their phone and it does look like they have GNU tools (as you said, `-printf` is a good indicator). Anyway, I never meant to imply you did anything wrong, I was just trying to clarify whether `-execdir` was indeed a GNU thing. I had already upvoted :) – terdon May 11 '21 at 17:02
  • Nitpick: Your solution (at lest the one using `find`) may find `index.html` as a regular file but the other file as a symbolic link to a regular file. This may be perfectly ok, but it may never be the other way around (`index.html` as a symbolic link). – Kusalananda May 12 '21 at 09:07
  • Yeah that is a very valid point you make. I will try to come up with an extension to accomodate it.Thanks for noticing it. – guest_7 May 12 '21 at 19:30
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 {} +
pLumo
  • 22,231
  • 2
  • 41
  • 66
  • Thank you very much pLumo, the search for directories works flawlessly :D – Tito May 11 '21 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 '21 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 '21 at 11:05
  • I'm really a novice in this, should I make these changes that @cas recommends? – Tito May 11 '21 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 '21 at 11:08
  • About a max of 100, is that big?@pLumo – Tito May 11 '21 at 11:10
  • @Tito it depends on how long it takes to fork sh 100 times on your iphone. that'll be about 100 times longer than it takes to fork sh once (the processing time of the shell script itself is negligible, the time used is almost entirely in the overhead of forking the sh interpreter). – cas May 11 '21 at 11:14
  • @cas I added the version with `-exec ... +`. Downside is, that you cannot use the return value anymore. Not a big deal for this task, but might be for other tasks. – pLumo May 11 '21 at 11:15
  • @cas, I have about 20 widgets on my device and it took about 2 seconds to find 6 matches – Tito May 11 '21 at 11:18
  • Thanks Stéphane, I did :-) – pLumo May 12 '21 at 06:29
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)
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
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
Kusalananda
  • 320,670
  • 36
  • 633
  • 936
  • @K. you need to take care of the max dir depth per OP's spec. – guest_7 May 11 '21 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 '21 at 06: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 '21 at 06: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 '21 at 08: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
         }
       }'
cas
  • 1
  • 7
  • 119
  • 185
  • Thank you very much cas but I can't use perl nor awk on this device ;) – Tito May 11 '21 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 '21 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 '21 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 '21 at 11:35
  • @terdon sorry, first post, I actually searched a unix tag but couldn't find – Tito May 11 '21 at 12:06
  • None of those seem relevant, really. Please [edit] your question and make it clear that you are not using the phone as a terminal only and connecting to another device but are actually running this on the phone, using iOS. – terdon May 11 '21 at 12:23
  • @terdon it's running some kind of `sh`. and `find` so it's kind of unixy. It doesn't have awk, though, which should disqualify it :-) – cas May 11 '21 at 12:26
  • Yeah, just looked it up. According to [Wikipedia](https://en.wikipedia.org/wiki/IOS), it's Unix-like, based on BSD so presumably close to macOS. And we have a tag for it. I guess we can assume BSD-flavor tools. – terdon May 11 '21 at 12:27
  • @terdon, I edited my post. Over the last year I've used many commands from this wonderful place and found that the most appropriate for iphone were Unix commands – Tito May 11 '21 at 13:34
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).

jcaron
  • 298
  • 1
  • 6