<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.1">Jekyll</generator><link href="https://www.taileb.name/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.taileb.name/" rel="alternate" type="text/html" /><updated>2023-06-29T10:05:04+00:00</updated><id>https://www.taileb.name/feed.xml</id><title type="html">Mehdi Taileb</title><subtitle>Multidisciplinary developer. FPGA, Python, Linux.</subtitle><entry xml:lang="en"><title type="html">An introduction to docker</title><link href="https://www.taileb.name/docker-intro" rel="alternate" type="text/html" title="An introduction to docker" /><published>2022-09-18T08:43:58+00:00</published><updated>2023-01-13T14:04:33+00:00</updated><id>https://www.taileb.name/docker-intro-en</id><content type="html" xml:base="https://www.taileb.name/docker-intro"><![CDATA[<p>In this post, I will write about docker and docker-compose as I learn them. I will try to explain the concepts and the commands I use. I will also try to explain the differences between docker and virtual machines.</p>
      <h2 id="introduction">Introduction</h2>
      <p>First of all let’s start with some definitions:</p>
      <blockquote>
        <p><strong>Containers</strong> are a way to package an application and its dependencies together. Containers are isolated from each other and bundle their own software, libraries and configuration files; they can communicate with each other through well-defined channels. All containers are run by a single operating system kernel and are thus more lightweight than virtual machines since they do not need to emulate a full hardware stack. <br />
          Containers are created from <strong>images</strong> that specify their precise contents. Images are often created by combining and modifying standard images downloaded from public repositories.</p>
      </blockquote>
      <blockquote>
        <p><strong>Docker</strong> is a set of platform as a service (PaaS) products that use container virtualization to deliver software in packages called containers.<br />
          Docker is a tool designed to make it easier to create, deploy, and run applications by using containers. Containers allow a developer to package up an application with all of the parts it needs, such as libraries and other dependencies, and ship it all out as one package. By doing so, thanks to the container, the developer can rest assured that the application will run on any other Linux machine regardless of any customized settings that machine might have that could differ from the machine used for writing and testing the code.</p>
      </blockquote>
      <p>To create an image from scratch using docker, you can use a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build you can create an automated build that executes several command-line instructions in succession.</p>
      <blockquote>
        <p><strong>Docker Compose</strong> is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.</p>
      </blockquote>
      <h2 id="docker-installation">Docker installation</h2>
      <p>To install docker on Fedora 36, you can use the following procedure:</p>
      <ul>
        <li>Install the docker repository:</li>
      </ul>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nt">-y</span> <span class="nb">install </span>dnf-plugins-core
<span class="nb">sudo </span>dnf config-manager <span class="se">\</span>
  <span class="nt">--add-repo</span> <span class="se">\</span>
  https://download.docker.com/linux/fedora/docker-ce.repo
</code></pre>
        </div>
      </div>
      <ul>
        <li>Install docker:</li>
      </ul>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>docker-ce docker-ce-cli containerd.io docker-compose-plugin
</code></pre>
        </div>
      </div>
      <ul>
        <li>Install docker-desktop:</li>
      </ul>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>docker-desktop-XX.YY.ZZ.x86_64.rpm
</code></pre>
        </div>
      </div>
      <h2 id="first-steps">First steps</h2>
      <p>To start docker, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>systemctl start docker
</code></pre>
        </div>
      </div>
      <p>To check if docker is running, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>systemctl status docker
</code></pre>
        </div>
      </div>
      <p>To enable docker at boot, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>systemctl <span class="nb">enable </span>docker
</code></pre>
        </div>
      </div>
      <p>To add your user to the docker group in order to run docker commands without sudo, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>usermod <span class="nt">-aG</span> docker <span class="nv">$USER</span>
</code></pre>
        </div>
      </div>
      <p>To run an example <em>hello-world</em> container, which is a simple container that prints a message, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>docker run hello-world
</code></pre>
        </div>
      </div>
      <script id="asciicast-a2hC6MM3aqk4yN30217b57K1u" src="https://asciinema.org/a/a2hC6MM3aqk4yN30217b57K1u.js" async=""></script>
      <p>As you can notice, the container image is downloaded from the docker hub, than a container is created from the downloaded image and launched. The docker hub is a registry of docker images. You can find a lot of images on the docker hub. You can also create your own images and upload them to the docker hub.</p>
      <p>To list all the containers, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>docker ps <span class="nt">-a</span>
</code></pre>
        </div>
      </div>
      <p>To remove a container, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>docker <span class="nb">rm</span> &lt;container_id&gt;
</code></pre>
        </div>
      </div>
      <h2 id="docker-images">Docker images</h2>
      <p>To list all the images, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker images
</code></pre>
        </div>
      </div>
      <p>To remove an image, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker rmi &lt;image_id&gt;
</code></pre>
        </div>
      </div>
      <h2 id="dockerfile">Dockerfile</h2>
      <p>To create a Dockerfile, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker build <span class="nt">-t</span> &lt;image_name&gt; <span class="nb">.</span>
</code></pre>
        </div>
      </div>
      <p>To run a container from an image, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker run <span class="nt">-d</span> <span class="nt">-p</span> 80:80 &lt;image_name&gt;
</code></pre>
        </div>
      </div>
      <p>The <code class="language-plaintext highlighter-rouge">-d</code> option runs the container in detached mode (the background). The <code class="language-plaintext highlighter-rouge">-p</code> option maps the container port 80 to the host port 80.</p>
      <h3 id="docker-file-example">Docker file example</h3>
      <p>In this example, we will create a docker image that runs a postgresql database. The docker image will be based on the official postgresql image. The docker image will be created using a Dockerfile.</p>
      <p>The Dockerfile:</p>
      <div class="language-dockerfile highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="k">FROM</span><span class="s"> postgres:latest</span>
<span class="k">ENV</span><span class="s"> POSTGRES_PASSWORD=postgres</span>
<span class="k">ENV</span><span class="s"> POSTGRES_USER=postgres</span>
<span class="k">ENV</span><span class="s"> POSTGRES_DB=postgres</span>
</code></pre>
        </div>
      </div>
      <p>The docker image can be created using the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker build <span class="nt">-t</span> postgresql <span class="nb">.</span>
</code></pre>
        </div>
      </div>
      <p>The -t option specifies the name of the image. The dot at the end of the command specifies the path to the Dockerfile.</p>
      <p>The docker image can be run using the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker run <span class="nt">-d</span> <span class="nt">-p</span> 5432:5432 postgresql
</code></pre>
        </div>
      </div>
      <p>The -d option runs the container in detached mode (the background). The -p option maps the container port 5432 to the host port 5432.</p>
      <h2 id="docker-compose">Docker compose</h2>
      <p>The docker compose tool is used to run multiple containers. The docker compose tool uses a docker-compose.yml file to define the containers and their configuration.</p>
      <h3 id="docker-compose-file-example">Docker compose file example</h3>
      <p>In this example, we will create a docker-compose.yml file that runs a postgresql database and a pgadmin4 container. The postgresql container will be based on the official postgresql image. The pgadmin4 container will be based on the official pgadmin4 image.</p>
      <p>The docker-compose.yml file:</p>
      <div class="language-yaml highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.8'</span>

<span class="na">services</span><span class="pi">:</span>
  <span class="na">postgresql</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">postgres:latest</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">postgresql</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">POSTGRES_PASSWORD</span><span class="pi">:</span> <span class="s">postgres</span>
      <span class="na">POSTGRES_USER</span><span class="pi">:</span> <span class="s">postgres</span>
      <span class="na">POSTGRES_DB</span><span class="pi">:</span> <span class="s">postgres</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">5432:5432</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">postgresql_data:/var/lib/postgresql/data</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">postgresql_network</span>

  <span class="na">pgadmin4</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">dpage/pgadmin4:latest</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">pgadmin4</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">PGADMIN_DEFAULT_EMAIL</span><span class="pi">:</span> <span class="s">admin@example.com</span>
      <span class="na">PGADMIN_DEFAULT_PASSWORD</span><span class="pi">:</span> <span class="s">admin</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">80:80</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">pgadmin4_data:/var/lib/pgadmin</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">postgresql_network</span>
      
<span class="na">volumes</span><span class="pi">:</span>
  <span class="na">postgresql_data</span><span class="pi">:</span>
  <span class="na">pgadmin4_data</span><span class="pi">:</span>

<span class="na">networks</span><span class="pi">:</span>
  <span class="na">postgresql_network</span><span class="pi">:</span>
</code></pre>
        </div>
      </div>
      <p>The docker-compose.yml file can be run using the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker compose up <span class="nt">-d</span> <span class="nt">--build</span>
</code></pre>
        </div>
      </div>
      <p>The -d option runs the containers in detached mode (the background). The –build option builds the containers before running them.</p>
      <p>I usually use custom names for docker compose files. For example, I use the following command to run the docker-compose-CUSTOM.yml file:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>  <span class="nb">sudo </span>docker compose <span class="nt">-f</span> docker-compose-CUSTOM.yml up <span class="nt">-d</span>
</code></pre>
        </div>
      </div>
      <h3 id="docker-compose-networks">Docker compose networks</h3>
      <p>Networks are used to connect containers. The docker compose tool creates a default network for the containers. The containers can be connected to the default network or to a custom network. The docker compose tool can create a custom network.</p>
      <p>A custom network can be created using the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker network create &lt;network_name&gt;
</code></pre>
        </div>
      </div>
      <p>The network can be removed using the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker network <span class="nb">rm</span> &lt;network_name&gt;
</code></pre>
        </div>
      </div>
      <p>The docker compose file can be used to create a custom network as shown in the postgresql and pgadmin4 example above.</p>
      <h3 id="docker-compose-volumes">Docker compose volumes</h3>
      <p>Volumes are used to store data. The docker compose tool can create volumes. The docker compose tool can also use existing volumes.</p>
      <p>A volume can be created using the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker volume create &lt;volume_name&gt;
</code></pre>
        </div>
      </div>
      <p>The volume can be removed using the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker volume <span class="nb">rm</span> &lt;volume_name&gt;
</code></pre>
        </div>
      </div>
      <p>The docker compose file can be used to create a volume as shown in the postgresql and pgadmin4 example above.</p>
      <h3 id="docker-compose-ports">Docker compose ports</h3>
      <p>Ports are used to expose the container ports to the host. The docker compose file can be used to expose the container ports as shown in the postgresql and pgadmin4 example above.</p>
      <h3 id="docker-compose-environment-variables">Docker compose environment variables</h3>
      <p>Environment variables are used to set the environment variables for the container. The docker compose file can be used to set the environment variables as shown in the postgresql and pgadmin4 example above.</p>
      <h3 id="docker-compose-restart-policy">Docker compose restart policy</h3>
      <p>The docker compose file can be used to set the restart policy for the container. The following restart policies are available:</p>
      <ul>
        <li>no - Do not restart the container if it stops or crashes</li>
        <li>on-failure - Restart the container if it stops or crashes, but only if the container exits with an error code</li>
        <li>always - Always restart the container if it stops or crashes</li>
        <li>unless-stopped - Always restart the container unless it is explicitly stopped</li>
      </ul>
      <p>The docker compose file can be used to set the restart policy as shown in the postgresql and pgadmin4 example above.</p>
      <h3 id="docker-compose-healthcheck">Docker compose healthcheck</h3>
      <p>The docker compose file can be used to set the healthcheck for the container. The following healthcheck options are available:</p>
      <ul>
        <li>disable - Disable the healthcheck</li>
        <li>interval - The interval between healthchecks</li>
        <li>timeout - The timeout for the healthcheck</li>
        <li>retries - The number of retries before the container is considered unhealthy</li>
        <li>start_period - The time to wait before starting the healthchecks</li>
        <li>test - The command to run to check the health</li>
        <li>none - Do not perform any healthchecks</li>
        <li>cmd - Run a command inside the container</li>
      </ul>
      <p>The following docker compose file can be used to set the healthcheck for the postgresql container:</p>
      <div class="language-yaml highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="na">healthcheck</span><span class="pi">:</span>
  <span class="na">test</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">CMD-SHELL"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">pg_isready</span><span class="nv"> </span><span class="s">-U</span><span class="nv"> </span><span class="s">postgres"</span><span class="pi">]</span>
  <span class="na">interval</span><span class="pi">:</span> <span class="s">10s</span>
  <span class="na">timeout</span><span class="pi">:</span> <span class="s">5s</span>
  <span class="na">retries</span><span class="pi">:</span> <span class="m">5</span>
  <span class="na">start_period</span><span class="pi">:</span> <span class="s">30s</span>
</code></pre>
        </div>
      </div>
      <p>The following command can be used to check if the postgresql container is healthy:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker inspect <span class="nt">--format</span><span class="o">=</span><span class="s1">''</span> postgresql
</code></pre>
        </div>
      </div>
      <p>If the container is healthy, the following output will be displayed:</p>
      <div class="language-json highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="p">{</span><span class="nl">"Status"</span><span class="p">:</span><span class="s2">"healthy"</span><span class="p">,</span><span class="nl">"FailingStreak"</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nl">"Log"</span><span class="p">:[{</span><span class="nl">"Start"</span><span class="p">:</span><span class="s2">"2021-07-05T13:59:00.000000000Z"</span><span class="p">,</span><span class="nl">"End"</span><span class="p">:</span><span class="s2">"2021-07-05T13:59:00.000000000Z"</span><span class="p">,</span><span class="nl">"ExitCode"</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nl">"Output"</span><span class="p">:</span><span class="s2">"2021-07-05 13:59:00.000 UTC [1] LOG:  listening on IPv4 address </span><span class="se">\"</span><span class="s2">
...
</span></code></pre>
        </div>
      </div>
      <h3 id="docker-compose-depends_on">Docker compose depends_on</h3>
      <h3 id="docker-compose-build">Docker compose build</h3>
      <h3 id="docker-compose-file-version">Docker compose file version</h3>
      <h3 id="docker-compose-file-reference">Docker compose file reference</h3>
      <h2 id="docker-cleanup">Docker cleanup</h2>
      <p>In most cases after you have finished with experiments with a docker compose file, you will have a lot of images, containers, volumes and networks that you don’t need anymore. To remove all the unused images, containers, volumes and networks, you can use the following command:</p>
      <div class="language-bash highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="nb">sudo </span>docker compose down <span class="nt">--rmi</span> all <span class="nt">--volumes</span> <span class="nt">--network</span> all <span class="nt">--remove-orphans</span>
</code></pre>
        </div>
      </div>
      <p>The –network all option removes all the networks. The –rmi all option removes all the images. The –volumes option removes all the volumes. The –remove-orphans option removes all the orphan containers.</p>
      ]]></content><author><name>Mehdi Taileb</name></author><category term="linux" /><category term="network administration" /><category term="software development" /><category term="docker" /><category term="docker-composer" /><summary type="html"><![CDATA[Notes on docker and docker-compose usage in a development environment on Fedora 36]]></summary></entry><entry xml:lang="en"><title type="html">How to accidentally delete all your data in Linux</title><link href="https://www.taileb.name/data-delete-accident" rel="alternate" type="text/html" title="How to accidentally delete all your data in Linux" /><published>2022-09-07T12:20:00+00:00</published><updated>2022-09-07T12:20:00+00:00</updated><id>https://www.taileb.name/data-delete-accident-en</id><content type="html" xml:base="https://www.taileb.name/data-delete-accident"><![CDATA[<p>Last friday I decided to automate my Jekyll blog deployment. I wanted to create a script that would build the site, commit the changes and push them to the remote repository. I was in a hurry and I didn’t want to spend too much time on it. I just wanted to get it done. So I wrote a bash script for this purpose.</p>
    <p>Unfortunately, the script had a small bug that I didn’t notice.</p>
    <p>I was using the rm command to delete the <code class="language-plaintext highlighter-rouge">../site/*</code>’ folder using the following command:</p>
    <div class="language-bash highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-rf</span> ../site/<span class="k">*</span>
</code></pre>
      </div>
    </div>
    <p>Later I added a an argument to the script to specify the path to the site folder and changed the command to:</p>
    <div class="language-bash highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-rf</span> <span class="nv">$2</span>/<span class="k">*</span>
</code></pre>
      </div>
    </div>
    <p>The <code class="language-plaintext highlighter-rouge">$1</code> argument is the command to run (build, serve, clean, deploy) and the <code class="language-plaintext highlighter-rouge">$2</code> argument is the path to the site folder. So the syntax of the script is:</p>
    <div class="language-bash highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code>./deploy.sh <span class="o">[</span><span class="nb">command</span><span class="o">]</span> <span class="o">[</span>path]
</code></pre>
      </div>
    </div>
    <p>The thing that I didn’t notice is that if I forget to specify the path argument, the script will delete file as if I had run the following command:</p>
    <div class="language-bash highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-rf</span> /<span class="k">*</span>
</code></pre>
      </div>
    </div>
    <p>As you can imagine, this is a very dangerous command. It will delete all the files in the root folder. And guess what? I did it. I ran the script without specifying the path argument and it deleted all the files in the root folder!!</p>
    <p>I than spent two days dealing with the <code class="language-plaintext highlighter-rouge">btrfs restore</code> and <code class="language-plaintext highlighter-rouge">btrfs-find-roots</code> command to try to save my <code class="language-plaintext highlighter-rouge">home</code> folder.</p>
    <p>I was lucky that I use cloud storage and I had a recent backup of my home folder. I was able to restore it and recover my files.</p>
    <p>I learned a lot from this experience. I will never forget to test the existence of the arguments of a script and set default values if an argument is missing. I will also never forget to use the <code class="language-plaintext highlighter-rouge">--dry-run</code> option of the <code class="language-plaintext highlighter-rouge">rm</code> command.</p>
    <p>For my case I used the following commands for my script:</p>
    <div class="language-bash highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code><span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"No path specified. Using default path: ../site"</span>
  <span class="nv">path</span><span class="o">=</span><span class="s2">"../site"</span>
<span class="k">else
  </span><span class="nv">path</span><span class="o">=</span><span class="nv">$2</span>
<span class="k">fi</span>
...
<span class="nb">echo</span> <span class="s2">"The following files will be deleted in </span><span class="nv">$path</span><span class="s2">:"</span>
<span class="nb">rm</span> <span class="nt">-rf</span> <span class="nt">--dry-run</span> <span class="nv">$path</span>/<span class="k">*</span>
<span class="nb">read</span> <span class="nt">-p</span> <span class="s2">"Continue with file deletion? (y/n) "</span> <span class="nt">-n</span> 1 <span class="nt">-r</span>
<span class="nb">echo
</span><span class="k">if</span> <span class="o">[[</span> <span class="nv">$REPLY</span> <span class="o">=</span>~ ^[Yy]<span class="nv">$ </span><span class="o">]]</span>
<span class="k">then
  </span><span class="nb">rm</span> <span class="nt">-rf</span> <span class="nv">$path</span>/<span class="k">*</span>
<span class="k">else
  </span><span class="nb">echo</span> <span class="s2">"Aborting..."</span>
  <span class="nb">exit </span>1
<span class="k">fi</span>
</code></pre>
      </div>
    </div>
    <p>I hope this will help you avoid making the same mistake as me.</p>
    ]]></content><author><name>Mehdi Taileb</name></author><category term="linux" /><category term="software development" /><category term="linux" /><category term="bash" /><category term="tips" /><summary type="html"><![CDATA[or how to avoid doing rm -rf /* in a script]]></summary></entry><entry xml:lang="en"><title type="html">An introduction to pandas</title><link href="https://www.taileb.name/pandas-intro" rel="alternate" type="text/html" title="An introduction to pandas" /><published>2022-08-31T12:00:00+00:00</published><updated>2022-08-31T12:00:00+00:00</updated><id>https://www.taileb.name/pandas-intro-en</id><content type="html" xml:base="https://www.taileb.name/pandas-intro"><![CDATA[<p>This post is not a tutorial but rather a collection of notes that I have taken while learning pandas. I will try to keep it updated as I learn more about pandas. I don’t want to write a tutorial because I want to keep it short and simple and mention only the features that I use most.</p>
  <h2 id="what-is--pandas">What is  pandas?</h2>
  <blockquote>
    <p><a href="https://pandas.pydata.org/">pandas</a> is a software library written for the Python programming language for data manipulation and analysis. In particular, it offers data structures and operations for manipulating numerical tables and time series. It is free software released under the three-clause BSD license.
      <cite><a href="https://en.wikipedia.org/wiki/Pandas_(software)">Wikipedia</a></cite></p>
  </blockquote>
  <p>Pandas has become the de facto standard for data analysis in Python. It is a powerful library that provides a high-performance, easy-to-use data structures and data analysis tools. It is built on top of NumPy and Matplotlib.</p>
  <h2 id="installation">Installation</h2>
  <p>A good practice is to create a virtual environment for each project. This will allow you to install the required packages without affecting the global Python installation. You can use the following command to create a conda environment:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>conda create <span class="nt">-n</span> pandas_intro <span class="nv">python</span><span class="o">=</span>3.10
conda activate pandas_intro
</code></pre>
    </div>
  </div>
  <p>Pandas is available on PyPI and can be installed using pip:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>pip <span class="nb">install </span>pandas
</code></pre>
    </div>
  </div>
  <p>Personally, I prefer using pandas in Jupyter Lab.
    The Jupyter Lab is a web application that allows you to create and run Jupyter notebooks.</p>
  <p>To install Jupyter Lab, you need to install the latest version of JupyterLab from the official repository.</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>pip <span class="nb">install </span>jupyterlab
</code></pre>
    </div>
  </div>
  <p>Or using the conda package:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>conda <span class="nb">install</span> <span class="nt">-c</span> conda-forge jupyterlab
</code></pre>
    </div>
  </div>
  <p>To launch Jupyter Lab, you need to start it from the command line:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>jupyter-lab
</code></pre>
    </div>
  </div>
  <p>After starting Jupyter Lab, you can open a new notebook and start writing your code your web browser.</p>
  <h2 id="importing-pandas">Importing pandas</h2>
  <p>A common practice is to import pandas as pd :</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>
</code></pre>
    </div>
  </div>
  <h2 id="pandas-data-frames">Pandas data frames</h2>
  <p>Pandas data frames are a powerful data structure that allow you to work with tabular data. They are similar to the tables in relational databases.</p>
  <p>Here is an example of a data frame created from a list of columns:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">([</span>
  <span class="p">[</span><span class="s">'A'</span><span class="p">,</span> <span class="s">'B'</span><span class="p">,</span> <span class="s">'C'</span><span class="p">],</span>
  <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span>
  <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span>
<span class="p">])</span>
</code></pre>
    </div>
  </div>
  <p>The data frame has three columns and three rows.</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">shape</span>
</code></pre>
    </div>
  </div>
  <p>Here is an example of a data frame created from a dictionary:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">({</span>
  <span class="s">'A'</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">],</span>
  <span class="s">'B'</span><span class="p">:</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span>
  <span class="s">'C'</span><span class="p">:</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">6</span><span class="p">]</span>
<span class="p">})</span>
</code></pre>
    </div>
  </div>
  <p>Pandas data frame can be created from an excel file:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">read_excel</span><span class="p">(</span><span class="s">'data.xlsx'</span><span class="p">)</span>
</code></pre>
    </div>
  </div>
  <p>The resulting data is a dictionary of data frames. The keys of the dictionary are the sheet names.</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">[</span><span class="s">'Sheet1'</span><span class="p">]</span>

<span class="k">for</span> <span class="n">df_sheet</span> <span class="ow">in</span> <span class="n">df</span><span class="p">:</span>
  <span class="k">print</span><span class="p">(</span><span class="n">df_sheet</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
</code></pre>
    </div>
  </div>
  <h2 id="data-frame-indexing-and-slicing">Data frame indexing and slicing</h2>
  <p>Data frames can be indexed by row and column.</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># row 0
</span><span class="n">df</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span> <span class="c1"># row 0, column 0
</span><span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># row 0
</span><span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="s">'A'</span><span class="p">]</span> <span class="c1"># row 0, column A
</span></code></pre>
    </div>
  </div>
  <p>I personally prefer to use the loc method because it is more intuitive.</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="s">'A'</span><span class="p">]</span> <span class="c1"># row 0, column A
</span><span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="p">[</span><span class="s">'A'</span><span class="p">,</span> <span class="s">'B'</span><span class="p">]]</span> <span class="c1"># row 0, columns A and B
</span></code></pre>
    </div>
  </div>
  <p>A data frame can be sliced by row and column:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># rows 0 and 1
</span><span class="n">df</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">:</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># rows 0 and 1, columns 0 and 1
</span><span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># rows 0 and 1
</span><span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span> <span class="s">'A'</span><span class="p">]</span> <span class="c1"># rows 0 and 1, column A
</span><span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span> <span class="p">[</span><span class="s">'A'</span><span class="p">,</span> <span class="s">'B'</span><span class="p">]]</span> <span class="c1"># rows 0 and 1, columns A and B
</span></code></pre>
    </div>
  </div>
  <h2 id="data-frame-basic-operations">Data frame basic operations</h2>
  <p>In this section, we will see how to perform basic operations on data frames.</p>
  <h3 id="adding-rows-and-columns">Adding rows and columns</h3>
  <p>To add a new row at the end of the data frame, you can use the append method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">df</span><span class="p">.</span><span class="n">append</span><span class="p">({</span>
  <span class="s">'A'</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>
  <span class="s">'B'</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
  <span class="s">'C'</span><span class="p">:</span> <span class="mi">9</span>
<span class="p">},</span> <span class="n">ignore_index</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># ignore_index=True to reset the index
</span></code></pre>
    </div>
  </div>
  <p>To add a new column, you can use the assign method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">df</span><span class="p">.</span><span class="n">assign</span><span class="p">(</span><span class="n">D</span><span class="o">=</span><span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">12</span><span class="p">])</span>
</code></pre>
    </div>
  </div>
  <p>To add a new row at a specific position, you can use the insert method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">df</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s">'E'</span><span class="p">,</span> <span class="p">[</span><span class="mi">13</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="mi">15</span><span class="p">])</span>
</code></pre>
    </div>
  </div>
  <h3 id="deleting-rows-and-columns">Deleting rows and columns</h3>
  <p>The drop method can be used to delete rows and columns:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">drop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># delete row 0
</span><span class="n">df</span><span class="p">.</span><span class="n">drop</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'A'</span><span class="p">],</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># delete column A
</span></code></pre>
    </div>
  </div>
  <p>Sometimes you may want to delete rows and columns that contain missing values.</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">dropna</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">how</span><span class="o">=</span><span class="s">'any'</span><span class="p">)</span> <span class="c1"># delete rows that contain missing values
</span><span class="n">df</span><span class="p">.</span><span class="n">dropna</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">how</span><span class="o">=</span><span class="s">'any'</span><span class="p">)</span> <span class="c1"># delete columns that contain missing values
</span></code></pre>
    </div>
  </div>
  <p>To drop a column than contains only missing values:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">dropna</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">how</span><span class="o">=</span><span class="s">'all'</span><span class="p">)</span> <span class="c1"># delete columns that do not contain missing values
</span></code></pre>
    </div>
  </div>
  <p>To drop duplicate rows:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">drop_duplicates</span><span class="p">(</span><span class="n">keep</span><span class="o">=</span><span class="s">'first'</span><span class="p">,</span> <span class="n">inplace</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># delete duplicate rows except the first one (inplace=True to modify the data frame)
</span></code></pre>
    </div>
  </div>
  <h3 id="editing-data">Editing data</h3>
  <p>To edit a cell, you can use the at method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">at</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span> <span class="c1"># replace data in row 0, column A with 0
</span></code></pre>
    </div>
  </div>
  <h3 id="replacing-data">Replacing data</h3>
  <p>To replace data in a data frame, you can use the replace method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="n">to_replace</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># replace 1 with 0
</span></code></pre>
    </div>
  </div>
  <p>A simpler and more intuitive way to replace data is to use the map method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">map</span><span class="p">({</span><span class="mi">1</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="p">:</span> <span class="mi">7</span><span class="p">})</span> <span class="c1"># replace 1 with 0 and 4 with 7
</span></code></pre>
    </div>
  </div>
  <p>To replace data in a column, you can use the replace method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'A'</span><span class="p">,</span> <span class="s">'B'</span><span class="p">)</span> <span class="c1"># replace A with B
</span></code></pre>
    </div>
  </div>
  <h3 id="string-operations">String operations</h3>
  <p>For string operations, you can use the str property methods:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">lower</span><span class="p">()</span> <span class="c1"># convert to lowercase
</span><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">upper</span><span class="p">()</span> <span class="c1"># convert to uppercase
</span><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># remove leading and trailing spaces
</span><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">' '</span><span class="p">)</span> <span class="c1"># split by space
</span><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">' '</span><span class="p">,</span> <span class="s">'_'</span><span class="p">)</span> <span class="c1"># replace space with underscore
</span><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">capitalize</span><span class="p">()</span> <span class="c1"># capitalize first letter
</span><span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">].</span><span class="nb">str</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="sa">r</span><span class="s">'(\d+)'</span><span class="p">,</span> <span class="sa">r</span><span class="s">'(\1)'</span><span class="p">,</span> <span class="n">regex</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># replace digits with parentheses using regex
</span></code></pre>
    </div>
  </div>
  <h3 id="filtering-rows-and-columns">Filtering rows and columns</h3>
  <p>A data frame can be filtered by a condition expressed as a boolean expression (a mask).</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">mask</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">'A'</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">1</span>
<span class="n">masked_df</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="n">mask</span><span class="p">]</span>
</code></pre>
    </div>
  </div>
  <h3 id="data-frame-sorting">Data frame sorting</h3>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">sort_values</span><span class="p">(</span><span class="n">by</span><span class="o">=</span><span class="s">'A'</span><span class="p">)</span> <span class="c1"># sort by column A
</span><span class="n">df</span><span class="p">.</span><span class="n">sort_values</span><span class="p">(</span><span class="n">by</span><span class="o">=</span><span class="p">[</span><span class="s">'A'</span><span class="p">,</span> <span class="s">'B'</span><span class="p">])</span> <span class="c1"># sort by columns A and B
</span></code></pre>
    </div>
  </div>
  <h3 id="data-frame-merging">Data frame merging</h3>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df1</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s">'A'</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="s">'B'</span><span class="p">:</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]})</span>
<span class="n">df2</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s">'A'</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="s">'B'</span><span class="p">:</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]})</span>
<span class="n">df1</span><span class="p">.</span><span class="n">merge</span><span class="p">(</span><span class="n">df2</span><span class="p">,</span> <span class="n">on</span><span class="o">=</span><span class="s">'A'</span><span class="p">)</span> <span class="c1"># merge on column A
</span></code></pre>
    </div>
  </div>
  <h3 id="data-frame-aggregation">Data frame aggregation</h3>
  <p>The groupby method can be used to group data by a column.
    In this example, the data is grouped by the first column and the mean of the second column is calculated:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">groupby</span><span class="p">(</span><span class="s">'A'</span><span class="p">).</span><span class="n">mean</span><span class="p">()</span> <span class="c1"># group by column A and compute mean
</span></code></pre>
    </div>
  </div>
  <h3 id="data-frame-concatenation">Data frame concatenation</h3>
  <p>To be able to concatenate data frames, the data frames must have the same columns.</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df1</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s">'A'</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="s">'B'</span><span class="p">:</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]})</span>
<span class="n">df2</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s">'A'</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="s">'B'</span><span class="p">:</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]})</span>
<span class="n">pd</span><span class="p">.</span><span class="n">concat</span><span class="p">([</span><span class="n">df1</span><span class="p">,</span> <span class="n">df2</span><span class="p">])</span> <span class="c1"># concatenate df1 and df2
</span></code></pre>
    </div>
  </div>
  <h2 id="data-frame-statistics">Data frame statistics</h2>
  <h3 id="statistics">Statistics</h3>
  <p>The describe method can be used to calculate basic statistics:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">describe</span><span class="p">()</span>
</code></pre>
    </div>
  </div>
  <h3 id="correlation">Correlation</h3>
  <p>Correlation is a measure of the strength of the relationship between two variables. intuitively, it is a measure of how much one variable is related to the other.</p>
  <p>In this example, correlation is the correlation between the first and second column:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">corr</span><span class="p">()</span>
</code></pre>
    </div>
  </div>
  <h2 id="data-frame-plotting">Data frame plotting</h2>
  <p>Pandas uses matplotlib to plot data frames.
    Pandas data frames can be plotted using the plot method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">kind</span><span class="o">=</span><span class="s">'scatter'</span><span class="p">,</span> <span class="n">x</span><span class="o">=</span><span class="s">'A'</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="s">'B'</span><span class="p">)</span>
</code></pre>
    </div>
  </div>
  <h2 id="data-frame-conversion">Data frame conversion</h2>
  <p>A data frame can be converted to a multitude of formats. Personally I convert data frames to dictionaries and lists.</p>
  <h3 id="converting-to-a-dictionary">Converting to a dictionary</h3>
  <p>To convert a data frame to a dictionary, you can use the to_dict method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">to_dict</span><span class="p">()</span>
</code></pre>
    </div>
  </div>
  <h3 id="exporting-to-a-json-file">Exporting to a json file</h3>
  <p>To export a data frame to a json file, you can use the to_json method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'data.json'</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
  <span class="n">df</span><span class="p">.</span><span class="n">to_json</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</code></pre>
    </div>
  </div>
  <p>If the data frame contains unicode characters, you can use the ensure_ascii parameter:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'data.json'</span><span class="p">,</span> <span class="s">'w'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
  <span class="n">df</span><span class="p">.</span><span class="n">to_json</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre>
    </div>
  </div>
  <h3 id="converting-to-a-list-of-dictionaries">Converting to a list of dictionaries</h3>
  <p>To convert a data frame to a list of dictionaries, you can use the to_dict method setting the orient parameter to records :</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">to_dict</span><span class="p">(</span><span class="n">orient</span><span class="o">=</span><span class="s">'records'</span><span class="p">)</span>
</code></pre>
    </div>
  </div>
  <h3 id="converting-to-a-list-of-lists">Converting to a list of lists</h3>
  <p>To convert a data frame to a list of lists, you can use the values method:</p>
  <div class="language-python highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="n">df</span><span class="p">.</span><span class="n">values</span><span class="p">.</span><span class="n">tolist</span><span class="p">()</span>
</code></pre>
    </div>
  </div>
  <h2 id="references">References</h2>
  <p>From the pandas documentation:</p>
  <ul>
    <li><a href="https://pandas.pydata.org/pandas-docs/stable/">Pandas documentation</a></li>
    <li><a href="https://pandas.pydata.org/Pandas_Cheat_Sheet.pdf">Pandas cheat sheet</a></li>
    <li><a href="https://pandas.pydata.org/pandas-docs/stable/user_guide/cookbook.html">Pandas cookbook</a></li>
    <li><a href="https://pandas.pydata.org/pandas-docs/stable/getting_started/tutorials.html">Pandas tutorial</a></li>
  </ul>
  <p>Articles:</p>
  <ul>
    <li><a href="https://kanoki.org/2019/08/03/add-new-rows-and-columns-to-pandas-dataframe/">Add new rows and columns to Pandas dataframe</a></li>
  </ul>
  <p>Tutorials:</p>
  <ul>
    <li><a href="https://www.tutorialspoint.com/python_pandas/index.htm">Python Pandas Tutorial</a></li>
    <li><a href="https://www.datacamp.com/community/tutorials/pandas-tutorial-dataframe-python">Pandas Tutorial: DataFrames in Python</a></li>
  </ul>
  ]]></content><author><name>Mehdi Taileb</name></author><category term="software development" /><category term="python" /><category term="pandas" /><category term="vs code" /><summary type="html"><![CDATA[A short introduction to pandas (personal notes)]]></summary></entry><entry xml:lang="en"><title type="html">How to install Flutter on Linux</title><link href="https://www.taileb.name/flutter-install" rel="alternate" type="text/html" title="How to install Flutter on Linux" /><published>2021-01-14T08:53:44+00:00</published><updated>2021-01-14T08:53:44+00:00</updated><id>https://www.taileb.name/flutter-install-en</id><content type="html" xml:base="https://www.taileb.name/flutter-install"><![CDATA[<p>This post covers the installation and configuration of Flutter on Linux Fedora 31/32 in order to develop desktop, web and Android applications. iOS support and other aspects of app development such as prototyping are omitted for now.</p>
  <p class="notice--info">The installation and configuration steps are deliberately given in the same order as during installation process on my computer.</p>
  <p>First, what is Flutter and why should it be considered?</p>
  <h2 id="what-is-flutter">What is Flutter?</h2>
  <blockquote>
    <p>Flutter SDK is Google’s UI toolkit for crafting beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
      <cite><a href="http://www.flutter.dev">Flutter.dev</a></cite></p>
  </blockquote>
  <blockquote>
    <p>Flutter is an open-source UI software development kit created by Google. It is used to develop applications for Android, iOS, Linux, Mac, Windows, Google Fuchsia, and the web from a single codebase. The first version of Flutter was known as codename “Sky” and ran on the Android operating system.
      <cite><a href="https://en.wikipedia.org/wiki/Flutter_(software)">Wikipedia</a></cite></p>
  </blockquote>
  <h2 id="why-i-chose-flutter">Why I chose Flutter</h2>
  <p>Two years ago I started developing web-based applications, primarily for telemetry applications and local SMBs.</p>
  <p>I didn’t face any major issues with backend development (mainly python-based) including but not limited to: database (SQL Alchemy and mongoDB), multi-threading and multiprocessing daemons, fault-tolerant protocol implementation, REST …</p>
  <p>The main issue I encountered in my journey to become a full-stack developer was the frontend side of the game</p>
  <p>I realized that the task of creating high-quality cross platform (web, android, iOS and PC) was tedious and difficult and required a large set of knowledge and skill sets that were far beyond what was possible to achieve as a solo-developer.</p>
  <p>In my journey I tested and used several solutions, most of which were for python developers wishing to do some GUI development. Some of the solutions which I used include:</p>
  <ul>
    <li><a href="https://riverbankcomputing.com/software/pyqt">PyQt</a> – a Python binding of the cross-platform GUI toolkit Qt</li>
    <li><a href="https://plotly.com/dash/">Plotly Dash</a> – python framework for building web analytic applications built on top of Flask, Plotly.js, and React.js</li>
    <li><a href="https://anvil.works/">Anvil</a> –  a free Python-based drag-and-drop web app builder</li>
    <li><a href="https://jupyter.org/">Jupyter notebooks</a> – an open-source web application that allows you to create and share documents that contain live code, equations, visualizations and narrative text.</li>
  </ul>
  <p>However, none of the options fully met my goals or solved the problems I faced. A solution based on a single language and a set of tools had to be found in order to cover all the tasks related to frontend design and interaction with the backend.</p>
  <p>That is why I decided to give Flutter a try and I was amazed by the quality of the tools and the fluidity of the workflow.</p>
  <h2 id="prerequisites">Prerequisites</h2>
  <h3 id="snap">snap</h3>
  <p>In order to install snap use the command line:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>snapd
</code></pre>
    </div>
  </div>
  <p>Then snap classic support should be enabled:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">sudo ln</span> <span class="nt">-s</span> /var/lib/snapd/snap /snap
</code></pre>
    </div>
  </div>
  <p>Snap Store application can also be installed from the command line:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">sudo </span>snap <span class="nb">install </span>snap-store
</code></pre>
    </div>
  </div>
  <h3 id="tools">tools</h3>
  <p>As stated by Flutter documentation, the following tools should be installed:</p>
  <ul>
    <li>bash</li>
    <li>curl</li>
    <li>file</li>
    <li>git 2.x</li>
    <li>mkdir</li>
    <li>rm</li>
    <li>unzip</li>
    <li>which</li>
    <li>xz-utils</li>
    <li>zip</li>
  </ul>
  <p>Also the <code class="language-plaintext highlighter-rouge">libGLU.so.1</code> library should be installed. The package providing this can be found using the command line:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>dnf provides libGLU.so.1
</code></pre>
    </div>
  </div>
  <p>To install:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>mesa-libGLU
</code></pre>
    </div>
  </div>
  <p class="notice--info">When using the <code class="language-plaintext highlighter-rouge">locate libGLU.so</code> command I realized that the <code class="language-plaintext highlighter-rouge">libGLU.so.1</code> file had been installed by snap. If asked I’ll check which of the installed libraries is being used by FLutter</p>
  <h2 id="flutter-installation">Flutter installation</h2>
  <p>In order to install Flutter use this in the command line:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">sudo </span>snap <span class="nb">install </span>flutter <span class="nt">--classic</span>
</code></pre>
    </div>
  </div>
  <p>Executing the following command will initialize the Flutter framework (download, install and initialize Dart and Flutter):</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>flutter sdk-path
</code></pre>
    </div>
  </div>
  <p>The command itself is used to display SDK’s path</p>
  <div class="notice--info">
    <p>As this post was being written, Flutter web apps support was in beta stage. To use it the latest Flutter version from the beta channel should be installed:</p>
    <div class="language-bash highlighter-rouge">
      <div class="highlight">
        <pre class="highlight"><code>flutter channel beta
flutter upgrade
flutter config <span class="nt">--enable-web</span>
</code></pre>
      </div>
  </div>
  </div>
  <h2 id="configuration">Configuration</h2>
  <p>The first thing to do is to add the path to the SDK to your <code class="language-plaintext highlighter-rouge">PATH</code> environment variable. This can be achieved by adding the following lines at the bottom of the <code class="language-plaintext highlighter-rouge">~/.bashrc</code> file:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># Flutter and Dart paths</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">:PATH_TO_FLUTTER_SDK/bin"</span>
</code></pre>
    </div>
  </div>
  <p>Remember to reload the terminal for the changes to take effect.</p>
  <p><code class="language-plaintext highlighter-rouge">~/.bashrc</code> can be patched and the terminal’s environment reloaded as follows:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">cat</span> <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh"> &gt;&gt; ~/.bashrc
# Flutter and Dart paths
export PATH="</span><span class="se">\$</span><span class="sh">PATH:`flutter sdk-path`/bin"
</span><span class="no">EOF
</span><span class="nb">.</span> ~/.bashrc
</code></pre>
    </div>
  </div>
  <p>The next big step of the configuration process is to run the  <code class="language-plaintext highlighter-rouge">flutter doctor</code> command from the command line to verify dependencies.</p>
  <p>Below is an example of the command output:
    <a href="/assets/images/flutter-install-doctor-1.png"><img src="/assets/images/flutter-install-doctor-1.png" alt="flutter doctor first run" /></a></p>
  <p>Several issues are to be noted:</p>
  <ul>
    <li>The Android SDK is not installed</li>
    <li>Android studio is not installed</li>
    <li>Visual studio code is detected but the Flutter extension is not installed</li>
    <li>No device is connected</li>
  </ul>
  <p class="notice--info">Device configuration and connection will be addressed in a separate post</p>
  <h2 id="additional-tools-installation">Additional tools installation</h2>
  <h3 id="visual-studio-code">Visual studio code</h3>
  <p>To install VS code on a Fedora machine:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">sudo </span>snap <span class="nb">install</span> <span class="nt">--classic</span> code
</code></pre>
    </div>
  </div>
  <p>Then the Flutter plugin should be installed:</p>
  <ul>
    <li>Open VS code</li>
    <li>Search for Flutter In the Extensions section</li>
    <li>Click install</li>
  </ul>
  <p>As shown in the image below, VS code is reported to be correctly configured:</p>
  <p><a href="/assets/images/flutter-install-doctor-2.png"><img src="/assets/images/flutter-install-doctor-2.png" alt="flutter doctor after vs code Flutter extension was installed" /></a></p>
  <h3 id="android-studio-and-android-sdk">Android Studio and Android SDK</h3>
  <p>Android Studio is the official Android app IDE based on <a href="https://www.jetbrains.com/idea/"><strong>IntelliJ IDEA</strong></a> from <a href="https://www.jetbrains.com">JetBrains</a>.</p>
  <p>you can use <code class="language-plaintext highlighter-rouge">snap</code> to install Android Studio:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">sudo </span>snap <span class="nb">install </span>android-studio <span class="nt">--classic</span>
</code></pre>
    </div>
  </div>
  <p class="notice--info">JetBrain’s <a href="https://www.jetbrains.com/toolbox-app/">Toolbox App</a> is another way to install Android Studio</p>
  <h4 id="android-studio-configuration">Android Studio configuration</h4>
  <p>When Android Studio is started for the first time the <em>Android Studio Setup Wizard</em> is automatically launched. Follow the instructions as shown in the following screenshots:
    <a href="/assets/images/flutter-install-androidstudio-setupwiz-1.png"><img src="/assets/images/flutter-install-androidstudio-setupwiz-1.png" alt="android studio setup wizard 1" /></a></p>
  <p>The Standard Install Type is similar to the Custom one:
    <a href="/assets/images/flutter-install-androidstudio-setupwiz-2.png"><img src="/assets/images/flutter-install-androidstudio-setupwiz-2.png" alt="android studio setup wizard 2" /></a></p>
  <p>I prefer the dark theme:
    <a href="/assets/images/flutter-install-androidstudio-setupwiz-3.png"><img src="/assets/images/flutter-install-androidstudio-setupwiz-3.png" alt="android studio setup wizard 3" /></a></p>
  <p>I left these options untouched:
    <a href="/assets/images/flutter-install-androidstudio-setupwiz-4.png"><img src="/assets/images/flutter-install-androidstudio-setupwiz-4.png" alt="android studio setup wizard 4" /></a></p>
  <p>Please take note of the paths used:
    <a href="/assets/images/flutter-install-androidstudio-setupwiz-5.png"><img src="/assets/images/flutter-install-androidstudio-setupwiz-5.png" alt="android studio setup wizard 5" /></a>
    <a href="/assets/images/flutter-install-androidstudio-setupwiz-6.png"><img src="/assets/images/flutter-install-androidstudio-setupwiz-6.png" alt="android studio setup wizard 6" /></a></p>
  <p>Depending on the Internet connection speed, the installation can take a while:
    <a href="/assets/images/flutter-install-androidstudio-setupwiz-7.png"><img src="/assets/images/flutter-install-androidstudio-setupwiz-7.png" alt="android studio setup wizard 7" /></a></p>
  <p>After successful configuration, the Android Studio Welcome window pops up:
    <a href="/assets/images/flutter-install-androidstudio-welcome.png"><img src="/assets/images/flutter-install-androidstudio-welcome.png" alt="android studio welcome" /></a></p>
  <p>The next step is to install the Flutter plugin for Android Studio. To do this, open the <code class="language-plaintext highlighter-rouge">Plugins</code> window by clicking on the <code class="language-plaintext highlighter-rouge">Configure</code> button, then click on <code class="language-plaintext highlighter-rouge">plugins</code>, then type <code class="language-plaintext highlighter-rouge">flutter</code> in the search bar, choose Flutter plugin from <code class="language-plaintext highlighter-rouge">flutter.dev</code> and hit <code class="language-plaintext highlighter-rouge">Install</code>:
    <a href="/assets/images/flutter-install-androidstudio-flutterplugin-install.png"><img src="/assets/images/flutter-install-androidstudio-flutterplugin-install.png" alt="install flutter plugin" /></a></p>
  <p>The Flutter plugin requires the Dart plugin to be installed:
    <a href="/assets/images/flutter-install-androidstudio-dartplugin-install.png"><img src="/assets/images/flutter-install-androidstudio-dartplugin-install.png" alt="flutter plugin requires dart plugin" /></a></p>
  <p>After successful installation of Dart and Flutter plugins, restart Android Studio:
    <a href="/assets/images/flutter-install-androidstudio-flutterplugin-install-restartide.png"><img src="/assets/images/flutter-install-androidstudio-flutterplugin-install-restartide.png" alt="flutter plugin requires ide restart" /></a></p>
  <p>Now that Android Studio is installed and configured, let’s check the output of <code class="language-plaintext highlighter-rouge">flutter doctor</code>:
    <a href="/assets/images/flutter-install-doctor-nolic.png"><img src="/assets/images/flutter-install-doctor-nolic.png" alt="flutter doctor no licenses" /></a></p>
  <p>So the next step is to accept Android licenses using <code class="language-plaintext highlighter-rouge">flutter doctor</code></p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>flutter doctor <span class="nt">--android-licenses</span>
</code></pre>
    </div>
  </div>
  <p>Use the following command to point <code class="language-plaintext highlighter-rouge">flutter doctor</code> to the installation path of Android Studio:</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>flutter config <span class="nt">--android-studio-dir</span><span class="o">=</span>/var/lib/snapd/snap/android-studio/current/android-studio/
</code></pre>
    </div>
  </div>
  <p>Now the <code class="language-plaintext highlighter-rouge">flutter doctor</code> output looks as follows:
    <a href="/assets/images/flutter-install-doctor-nodart-noflutter.png"><img src="/assets/images/flutter-install-doctor-nodart-noflutter.png" alt="flutter doctor no dart no flutter" /></a></p>
  <p>You can be safely ignore the two errors stating that Flutter and Dart plugins are not detected.</p>
  <h2 id="whats-next">What’s next?</h2>
  <p>The next steps may include configuring and testing the Android Emulator, configuring on-device debugging, developing basic Flutter apps to explore different aspects of the SDK, exploring the features of VS code and Android Studio and exploring debugging tools.</p>
  <h2 id="references">References</h2>
  <p>Further details could be found in the following web resources:</p>
  <ul>
    <li><a href="https://snapcraft.io/install/android-studio/fedora">How to install Android Studio on Fedora</a></li>
    <li><a href="https://snapcraft.io/docs/installing-snap-on-fedora">Installing snap on Fedora</a></li>
    <li><a href="https://flutter.dev/docs/get-started/install">Flutter documentation</a></li>
    <li>Flutter documentation – <a href="https://flutter.dev/docs/get-started/editor?tab=vscode">Set up an editor</a></li>
    <li>Pointing the Android Studio installation directory in Flutter (<a href="https://github.com/flutter/flutter/issues/67986">GitHub issue</a>)</li>
    <li><a href="https://flutter.dev/docs/get-started/web">Building a web application with Flutter</a></li>
    <li><a href="https://developer.android.com/studio/run/emulator-acceleration?utm_source=android-studio#vm-linux">Configure VM acceleration on Linux</a></li>
  </ul>
  <!-- 

![Name of Link]({% post_url 2010-07-21-name-of-post %})

-->]]></content><author><name>Mehdi Taileb</name></author><category term="software development" /><category term="flutter" /><category term="dart" /><category term="install" /><category term="android" /><category term="android studio" /><category term="vs code" /><summary type="html"><![CDATA[Installation and configuration on Fedora 31/32]]></summary></entry></feed>