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 Commented May 11, 2021 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. Commented May 11, 2021 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? Commented May 11, 2021 at 10:42
  • @terdon It's running on the iPhone (jailbroken) Commented May 11, 2021 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. Commented May 11, 2021 at 13:56

6 Answers 6

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" 
8
  • 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. Commented May 11, 2021 at 14:12
  • @terdon, -execdir was not a GNU extension initially. OpenBSD had it 9 years before GNU find (1996 vs 2005) Commented May 11, 2021 at 14:58
  • @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). Commented May 11, 2021 at 15:02
  • @StéphaneChazelas according to the OP 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. Commented May 11, 2021 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 Commented May 11, 2021 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 {} + 
9
  • Thank you very much pLumo, the search for directories works flawlessly :D Commented May 11, 2021 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 . Commented May 11, 2021 at 10:59
  • @Tito if the answer has helped you, you should consider accepting it by hitting the checkmark on the left side :-) Thanks. Commented May 11, 2021 at 11:05
  • I'm really a novice in this, should I make these changes that @cas recommends? Commented May 11, 2021 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 ... Commented May 11, 2021 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. Commented May 11, 2021 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. Commented May 12, 2021 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. Commented May 12, 2021 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"). Commented May 12, 2021 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 ;) Commented May 11, 2021 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? Commented May 11, 2021 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, Commented May 11, 2021 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. Commented May 11, 2021 at 11:35
  • @terdon sorry, first post, I actually searched a unix tag but couldn't find Commented May 11, 2021 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 Commented May 13, 2021 at 12:40

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.