find + mkdir are Turing complete
Introduction
We will show that a system with only GNU commands find
And mkdir
is Turing complete.
It is well known that teams sed
And awk
are Turing complete themselves, but I couldn't find any information about Turing completeness find
+ mkdir
.
The proof is based on the implementation tag systems.
We will consider the implementation of the cycle, FizzBuzz and tag system in order.
Reference materials
Implementation
Cycle construction
The code below recursively creates folders and enters an infinite loop:
mkdir x
find x -execdir mkdir x/x \;
find x
returns a list of files in x
including x
. When outputting in a list x
is starting up mkdir
for creation x/x
which is then included in the next iteration find
leading to the creation x/x/x
and so on.
To limit the depth of folder creation, we can use the option -maxdepth
:
mkdir x
find x -maxdepth 3 -execdir mkdir x/x \;
This code will stop executing after creation x/x/x/x/x
. Replacing 3
on N
we will get N+2
folder levels x
.
FizzBuzz
Option -regex
teams find
allows you to filter the names of files that will be subject to subsequent actions. Thanks to this, we can filter the quantities x/
multiples of 3, 5 and 15, and by combining this with a loop, you can implement FizzBuzz. In the code shown below -regextype posix-extended
is used for readability, but the same can theoretically be achieved with any regular expression syntax.
mkdir -p d/x
find d/x -regextype posix-extended -regex 'd(/x){0,29}' -execdir mkdir x/x \;
find d -regextype posix-extended \
-regex 'd((/x){15})+' -printf "FizzBuzz\n" -o \
-regex 'd((/x){5})+' -printf "Buzz\n" -o \
-regex 'd((/x){3})+' -printf "Fizz\n" -o \
-regex 'd(/x)+' -printf "%d\n"
The second line creates x/... /x
(30 x
) inside d
after which the third line displays FizzBuzz
if the quantity x
turns out to be a multiple of 15, Buzz
if a multiple of 5, Fizz
if it is a multiple of 3, otherwise the file depth relative to d
that is, the number x
.
Implementation of tag system
The tag system is a triplet (m,A,P)
Where
m
— is a positive integer.A
— the final alphabet containing the stop symbolH
.P
— the generating rule for each alphabet.
Once the source string is received, the system repeatedly performs the following actions:
If the string length is less than
m
or its initial symbolx
equalH
then a stop is performed. OtherwiseP(x)
is added to the end.The first ones are removed
m
characters.
and outputs a string when stopped.
It is known that there is a tag system with m=2,∣A∣=576
which implements a universal Turing machine (De Mol, L., 2008), so a system capable of handling a tag system of this size is Turing complete.
Here we use example tag systems with m=2,∣A∣=4
from Wikipedia, and we will show its implementation.
The basic idea is to iteratively append the next state file path to the state file path using a separator _
. For example, if the state _/b/a/a/_
the next state is in progress a/c/c/a
then the resulting path to the file will look like this _/b/a/a/_/a/c/c/a/_
and this process is repeated until the system stops. Here is the execution time in the folder no more than one file is created.
# Демонстрация таг-системы с m=2, реализованной при помощи только `find` и `mkdir`,
# на основе примера из Википедии.
# en.wikipedia.org/wiki/Tag_system#Example:_A_simple_2-tag_illustration
# '_' используются как разделители между состояниями.
mkdir _
# Порождающие правила для таг-системы, задаваемые как константы.
M=2
PROD_A="c/c/b/a/H"
PROD_B="c/c/a"
PROD_C="c/c"
# Исходное состояние (окружённое _).
mkdir -p /b/a/a/
# Алфавиты (постоянного размера)
S="(/[abcH])"
# Продолжаем добавлять следующее состояние в конец состояния, пока не встретим символ останова
# /b/a/a/ -> /b/a/a//a/c/c/a/_ -> /b/a/a//a/c/c/a//c/a/c/c/b/a/H/ -> ... -> ...//H/c/c/c/c/c/c/a/ (halt)
#
# Строка 2: условие останова
# Строки 3-6: копируем предыдущее состояние, удаляя M символов из начала благодаря использованию обратных ссылок.
# Строки 7-29: применяем порождающее правило для a, b, c.
find _ -regextype posix-extended -empty (
-regex "._/H.|.*(/[^/]?){, -prune -o \
-regex ".*_" class="formula inline">S{" class="formula inline">S)/a \( -execdir mkdir a/a b/a c/a H/a _/a \; -o -true \) -o \
-regex ".*_" class="formula inline">S{" class="formula inline">S)/b \( -execdir mkdir a/b b/b c/b H/b _/b \; -o -true \) -o \
-regex ".*_" class="formula inline">S{" class="formula inline">S)/c \( -execdir mkdir a/c b/c c/c H/c _/c \; -o -true \) -o \
-regex ".*_" class="formula inline">S{" class="formula inline">S)/H \( -execdir mkdir a/H b/H c/H H/H _/H \; -o -true \) -o \
\( \
-regex ".*_/a" class="formula inline">S/ \( \
-execdir find a \; -execdir mkdir -p a/" class="formula inline">PROD_A/ ; -o
-execdir find b ; -execdir mkdir -p b/mkdir -p c/" class="formula inline">PROD_A/_ ; -o
-execdir find H ; -execdir mkdir -p H/mkdir -p _/" class="formula inline">PROD_A/_ ;
) -o
-regex "./b" class="formula inline">S*" (
-execdir find a ; -execdir mkdir -p a/mkdir -p b/" class="formula inline">PROD_B/ ; -o
-execdir find c ; -execdir mkdir -p c/mkdir -p H/" class="formula inline">PROD_B/_ ; -o
-execdir find _ ; -execdir mkdir -p /".*_/c" class="formula inline">S*/ \( \
-execdir find a \; -execdir mkdir -p a/" class="formula inline">PROD_C/_ ; -o
-execdir find b ; -execdir mkdir -p b/mkdir -p c/" class="formula inline">PROD_C/_ ; -o
-execdir find H ; -execdir mkdir -p H/mkdir -p _/" class="formula inline">PROD_C/_ ;
)
)
) &> /dev/null
# Выводим результат. (/H/c/c/c/c/c/c/a/)
find _ -depth ! -empty -name _ -execdir find _ -empty ; -quit
We see that the expected result is indeed displayed. /H/c/c/c/c/c/c/a/
. In the FizzBuzz implementation we used regular expressions, but here, as you can see, we use backreferences (\2
), thanks to which it is possible to copy the previous state without the first m
characters.
Obviously, this construction can be extended to a larger (unchanging) alphabet size. (The problem of running out of characters can easily be solved by giving each file a name of several characters.)
Hence, find
+ mkdir
Turing complete.
Expected questions and answers to them
Is it possible that we cannot execute an arbitrary size automaton due to file path length limitations?
I don't think so.
mkdir
will fail if passed a path to a file of a certain length or longer, but in the code we carefully avoid passing it directlymkdir
paths to files of arbitrary length. According to my tests,find
It works even when the paths are longer than 30000, and I couldn't find the limit.
Does the POSIX specification guarantee that this code will work?
Unfortunately, no. It clearly states that the behavior is not covered by the specification if a file is added to a directory that is searched at runtime (source). I have not tested the behavior of any tools other than GNU.
I used the following versions of the tools:
-> % find --version | head -1 && mkdir --version | head -1 && uname -a
find (GNU findutils) 4.8.0
mkdir (GNU coreutils) 8.32
Linux DESKTOP-5JU1LI7 5.15.153.1-microsoft-standard-WSL2 #1 SMP Fri Mar 29 23:14:13 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux