{"id":19041618,"url":"https://github.com/mikeizbicki/lab-posix-mapreduce","last_synced_at":"2026-01-28T04:48:12.737Z","repository":{"id":219225480,"uuid":"748012925","full_name":"mikeizbicki/lab-posix-mapreduce","owner":"mikeizbicki","description":null,"archived":false,"fork":false,"pushed_at":"2025-02-07T19:50:23.000Z","size":21,"stargazers_count":0,"open_issues_count":0,"forks_count":77,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-07T20:32:38.585Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mikeizbicki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-25T04:50:11.000Z","updated_at":"2025-02-07T19:50:27.000Z","dependencies_parsed_at":"2024-11-08T22:30:11.914Z","dependency_job_id":"2e778257-54f9-4639-b3d1-ff78b3210bbe","html_url":"https://github.com/mikeizbicki/lab-posix-mapreduce","commit_stats":null,"previous_names":["mikeizbicki/lab-gnuplot","mikeizbicki/lab-posix-mapreduce"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeizbicki%2Flab-posix-mapreduce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeizbicki%2Flab-posix-mapreduce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeizbicki%2Flab-posix-mapreduce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeizbicki%2Flab-posix-mapreduce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikeizbicki","download_url":"https://codeload.github.com/mikeizbicki/lab-posix-mapreduce/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240100512,"owners_count":19747683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T22:30:06.660Z","updated_at":"2026-01-28T04:48:07.700Z","avatar_url":"https://github.com/mikeizbicki.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lab: POSIX MapReduce\n\n\u003c!--\nREMINDER TO MYSELF:\nThe `notes` branch contains some partially completed lab material.\n--\u003e\n\nRecall that in last week's lab, we saw how to use python and SQL to perform the *count distinct* and *count group* queries.\nWe had two main takeaways:\n\n1. Python's pandas library requires $\\Omega(n)$ memory,\n    and so it is not suitable for large datasets.\n1. SQLite requires only $O(1)$ memory,\n     and so is suitable for large datasets.\n\nIn this lab, we will see how to run those same queries in the shell.\nWe'll also see how to make nice visualizations of those queries using the terminal program gnuplot,\nand parallelize those queries using MapReduce.\n\n## Part 0: Setup\n\nFork this repo, clone your fork onto the lambda server, and cd into the repo directory.\nIt will be important later that you are working on your forked repo because you'll be uploading images to the repo for your submission.\n\n## Part 1: Count Distinct/Group in the Shell\n\nThe `uniq` and `sort` commands are included in every POSIX compliant operating system.\nThey are commonly used together to perform the count distinct and count group operations.\n\n### Part 1.a: Count Distinct\n\nThe `uniq` command (pronounced like \"unique\") filters its input to remove any line that is the same as its previous line.\nWe'll use the `colors` file included in the repo as an example.\nCompare the output of the following two commands.\n```\n$ cat colors\n$ cat colors | uniq\n```\nNotice that every line in the output of `uniq` is not necessarily \"unique\",\nbut no two neighboring lines are the same.\n\nOne common strategy for outputting only the distinct lines is to first sort the input.\n```\n$ cat colors | sort | uniq\n```\nNow notice that there are no duplicate colors in the output.\nWe can complete our count distinct query by combining with `wc -l`:\n```\n$ cat colors | sort | uniq | wc -l\n```\n\n\u003e **Note:**\n\u003e Unfortunately, the `sort` command above is rather expensive.\n\u003e Sorting requires $\\Omega(n)$ memory and $\\Omega(n\\log n)$ compute,\n\u003e and the `sort` command is the only commonly used shell command that does not use `O(1)` memory.\n\u003e Still, the `sort` command is about as efficient as theoretically possible.\n\u003e It uses an [external sort algorithm](https://en.wikipedia.org/wiki/External_sorting),\n\u003e which is something that you probably did not study in data structures.\n\u003e External sort is very similar to merge sort, except that the intermediate steps are stored on the hard drive.\n\u003e This allows `sort` to sort extremely large files even on systems with limited RAM.\n\n### Part 1.b: Count Group\n\nThe count group query is almost as easy to do.\nThe `uniq` program with the `-c` flag counts the total number of duplicated rows,\nand so adding this flag performs the count group query.\n```\n$ cat colors | sort | uniq -c\n```\nNotice, however, that the output above is not sorted by the number of occurrences.\nBut sorted output would be easier to read, so let's figure out how to get it.\n\nYou might think that another call to `sort` would do the trick.\nTry it:\n```\n$ cat colors | sort | uniq -c | sort\n```\nTechnically, this output is now sorted [ASCIIbeticallly](https://en.wiktionary.org/wiki/ASCIIbetical).\n(The `1` character comes before ` ` in ASCII, and so `11` comes before ` 1`.)\nBut this isn't what we really wanted.\nWe want to sort on the numerical values of the numbers in our table,\nand not their ASCII values.\n`sort` has a flag `-n` for this purpose.\nAdding it to our command, we can now perform a nice count group query on `colors` with the command\n```\n$ cat colors | sort | uniq -c | sort -n\n```\n\n\u003e **Exercise:**\n\u003e Use output redirection to store the output of the command above in a file called `colors.dat`.\n\u003e We will use this file to experiment with plotting in the next section.\n\n## Part 2: Plotting with Gnuplot\n\nIt is common to plot the results of a count group query as a bar chart.\nIn this section, we'll see how to do that with gnuplot.\nGnuplot is a popular program for generating charts on the terminal.\nIt is [the recommended tool for generating plots for wikipedia](https://en.wikipedia.org/wiki/Wikipedia:How_to_create_charts_for_Wikipedia_articles#Plotting),\nand [most plots on wikipedia were generated with gnuplot](https://commons.wikimedia.org/wiki/Category:Gnuplot_diagrams).\n\n\u003e **Note:**\n\u003e The [GNU project](https://www.gnu.org/) is an open source rewrite of the Unix operating system,\n\u003e and all of the terminal commands we have used so far are part of GNU.\n\u003e [Gnuplot, however, is not affiliated with the GNU project](http://www.gnuplot.info/faq/#x1-120001.7).\n\u003e Therefore, even though [GNU is pronounced with a hard G](https://www.gnu.org/gnu/pronunciation.html),\n\u003e gnuplot is canonically pronounced as \"newplot\" using the standard English pronunciation of gnu.\n\u003e [The authors have a detailed FAQ answer about the origin of the name.](http://www.gnuplot.info/faq/#x1-70001.2):\n\n### Part 2.a: A First Attempt at Plotting\n\nStart the gnuplot program.\n```\n$ gnuplot\n```\nYou should get a large welcome message printed followed by a new prompt `gnuplot\u003e` indicating that you are now typing gnuplot commands instead of shell commands.\n\nThe simplest command is the `plot` command,\nwhich just takes as input a filename of data to plot.\nTry it.\n```\ngnuplot\u003e plot 'colors.dat'\n```\nYou likely get an error similar to\n```\nqt.qpa.screen: QXcbConnection: Could not connect to display \nCould not connect to any X display.\n```\nThis error message references the [X Window System](https://en.wikipedia.org/wiki/X_Window_System),\nwhich is a popular system for displaying graphics on Linux machines.\nOne of its main advantages is that it allows windows created by remote machines (like the lambda server) to be displayed on your local machine.\n\nIf your laptop is a linux machine, then you already have X Windows installed.\nYou can solve this problem by enabling \"X forwarding\" with the [`-XY` flags](https://explainshell.com/explain?cmd=ssh+-X+-Y+user%40host) in your ssh command.\n\nThere exist open source X Windows implementations for every operations system.\n[Xming](http://www.straightrunning.com/XmingNotes/) is the most popular one for Windows,\nand [XQuartz](https://www.xquartz.org/) for Mac.\nIf you installed one of these on your laptop, then you would also be able to open windows on the lambda server and have them appear on your machine.\nFor this lab, however, you don't need to install this software if you don't want to.\nWe'll see alternative ways to get access to the plots.\n\n\u003e **Note:**\n\u003e Many people think that X Windows gets its name from an allusion to Microsoft Windows graphical interface.\n\u003e But the opposite is closer to the truth.\n\u003e The first version of X Windows was released in 1984,\n\u003e and the first version of Microsoft Windows was released on 1985.\n\n### Part 2.b: Plotting with ASCII Art\n\nThe easiest way to view the plots is to plot them directly in the terminal with ASCII art.\n\nThis is a good time to mention that the word *terminal* has a different meaning in the context of gnuplot.\nRecall that in most contexts, a terminal is the graphical program on your computer that use when interacting with the shell.\nIn gnuplot, however, a [*terminal* refers to the graphical engine used to render the plot](http://gnuplot.info/docs_5.5/Terminals.html).\nThe [default terminal is called `qt`](http://gnuplot.info/docs_5.5/loc21993.html) because it uses the [QT library](https://en.wikipedia.org/wiki/Qt_(software)) to render the plot to the X Windows system.\nWe can change the terminal to the [dumb terminal](http://www.gnuplot.info/docs_4.2/node367.html) to get the contents of our plot printed as ASCII art.\n\nThe following commands should work for everyone.\n```\ngnuplot\u003e set terminal dumb\ngnuplot\u003e plot 'colors.dat'\n  12 +---------------------------------------------------------------------+\n     |                      +                       +                      |\n     |                                                                     |\n  10 |-+                                                                 +-|\n     |                                                                     |\n     |                                                                     |\n     |                                                                     |\n   8 |-+                                                                 +-|\n     |                                                                     |\n     |                                                                     |\n   6 |-+                                                                 +-|\n     |                                                                     |\n     |                                                                     |\n   4 |-+                                            A                    +-|\n     |                                                                     |\n     |                      A                                              |\n     |                                                                     |\n   2 |-+                                                                 +-|\n     |                                                                     |\n     |                      +                       +                      |\n   0 +---------------------------------------------------------------------+\n  yellow                  green                    red                   blue\n```\nThis plot is hard to read, and the values for `yellow` and `blue` aren't even being displayed because they overlap with the axes.\nWe can make this plot a little bit nicer by adding some more formatting commands.\n```\ngnuplot\u003e set style data histogram\ngnuplot\u003e set style fill solid border -1\ngnuplot\u003e plot 'colors.dat' using 1:xtic(2) notitle\n  12 +---------------------------------------------------------------------+\n     |             +             +             +             +             |\n     |                                                       ******        |\n  10 |-+                                                     *    *      +-|\n     |                                                       *    *        |\n     |                                                       *    *        |\n     |                                                       *    *        |\n   8 |-+                                                     *    *      +-|\n     |                                                       *    *        |\n     |                                                       *    *        |\n   6 |-+                                                     *    *      +-|\n     |                                                       *    *        |\n     |                                                       *    *        |\n   4 |-+                                       ******        *    *      +-|\n     |                                         *    *        *    *        |\n     |                           ******        *    *        *    *        |\n     |                           *    *        *    *        *    *        |\n   2 |-+                         *    *        *    *        *    *      +-|\n     |             ******        *    *        *    *        *    *        |\n     |             *    *        *    *        *    *        *    *        |\n   0 +---------------------------------------------------------------------+\n                yellow         green          red          blue\n```\nThe `set style` commands change the formatting to use a bar plot instead of a line plot.\nThe `using 1:xtic(2)` tells gnuplot that the first column in the datafile should be the height of the bars,\nand the second column should be the label on the x-axis.\nFinally, the `notitle` command removes the legend.\n\n### Part 2.c: Generating PNG Files\n\nThis second plot is a little bit nicer,\nbut it's still not very good since the results are limited to ASCII art.\nTo generate a proper plot, we can use the `png` terminal.\n\nThe following commands will replot the graph above and store it in the file `colors.png`.\n(There is no need to retype the `set style` commands, as those will remain in effect.)\n```\ngnuplot\u003e set terminal png size 800,400\ngnuplot\u003e set output 'colors.png'\ngnuplot\u003e plot 'colors.dat' using 1:xtic(2) notitle\n```\nThe `plot` command above should have no output.\nLeave the gnuplot shell by typing `^D` and run `ls`.\nYou should see the file `colors.png` was created in your lab folder.\n\n\u003e **Note:**\n\u003e `png` is [officially pronounced like \"ping\"](https://en.wikipedia.org/wiki/PNG).\n\u003e So `colors.png` is pronounced as \"colors dot ping\" and not \"colors dot p n g\".\n\n### Part 2.d: Viewing the Image\n\nUnfortunately, there's no way to view png files inside the terminal.\nTo view the `colors.png` file, you will need to transfer it to you computer.\n\nOn Linux machines, this is once again trivial.\nThe `sshfs` program lets you [mount](https://unix.stackexchange.com/questions/3192/what-is-meant-by-mounting-a-device-in-linux) the lambda server's filesystem onto your own filesystem.\nFor example, if I run the following command on my laptop:\n```\n$ sshfs -p 5055 csci143example@lambda.compute.cmc.edu:/home/csci143example ~/lambda\n```\nThen the folder `~/lambda` on my laptop will contain all of the contents of my home folder `/home/csci143example` on the lambda server.\nI can then navigate to that folder using my standard file explorer tools to view the file.\n\n\u003e **Note:**\n\u003e There exist sshfs implementations for [Mac](https://osxfuse.github.io) and [Windows](https://github.com/winfsp/sshfs-win).\n\u003e You are not required to download and install them,\n\u003e but they may make working on the lambda server easier for you.\n\nIf you don't have `sshfs` installed, then the next best option is to use github to transfer the file.\nYou can upload the file to github with the following commands.\n```\n$ git add colors.png\n$ git commit -m 'added colors.png'\n$ git push origin master\n```\nIf these commands worked successfully,\nthen the image below should work.\n\n\u003cimg src=colors.png /\u003e\n\nIf not, ensure that you're looking at your forked repo and not my repo.\n\nDon't move on to the next steps until you're successfully able to view your image.\nPart of the submission for this lab will require that all of these broken image links are replaced with working images.\n\n## Part 3: Writing Gnuplot Scripts\n\nWorking with gnuplot through the terminal interface is possible,\nbut it's annoying to have to retype all of those commands anytime we want to make a plot.\nA better solution is to automate plotting with a script.\nWe will now see how to write and use these scripts.\n\nCreate a file `boxplot.gp` with the following contents.\n```\nset terminal png size 800,400\nset output 'colors.png'\nset style data histogram\nset style fill solid border -1\nplot 'colors.dat' using 1:xtic(2) notitle\n```\nNotice that these are just the commands that we previously typed directly into the gnuplot terminal.\nWe can now run all of these commands at once by passing the `-c boxplot.gp` arguments to gnuplot.\n\nFirst, delete the `colors.png` file.\n```\n$ rm colors.png\n$ ls\n```\nThen run the script and verify that it worked by seeing that it recreated the file.\n```\n$ gnuplot -c boxplot.gp\n$ ls\n```\n\nJust like python functions are more useful when they take parameters that adjust how they work,\nscripts are also more useful when they take input.\nWe will modify the script so that it can be used with the pipe.\nThis will require two changes:\n\n1. Replace the hard coded output filename `'colors.png'` with the variable `ARG1`.\n    Gnuplot will substitute the first command line argument of the script with this value.\n1. Replace the hard coded input filename `'colors.dat'` with the special filename `'/dev/stdin'`.\n    This will allow us to get our data from the pipe.\n\nAfter making these changes, the final `boxplot.gp` script should look like:\n```\nset terminal png size 800,400\nset output ARG1\nset style data histogram\nset style fill solid border -1\nplot '/dev/stdin' using 1:xtic(2) notitle\n```\nTo test this new script, we will recreate the original `colors.png` file.\n```\n$ rm colors.png\n$ ls\n$ cat 'colors.dat' | gnuplot -c boxplot.gp colors.png\n$ ls\n```\nAs before, you should verify that the output of the first `ls` and second `ls` differ only by the newly created `colors.png` file.\n\n## Part 4: Bigger Data\n\nRecall that the file `/data/Twitter dataset/geoTwitter20-01-01.zip` contains all of the geolocated tweets sent on January 1st 2020.\nLet's do an analysis to see how many tweets were sent from each country on this day.\nWe can easily do this with the following shell 1-liner:\n```\n$ unzip -p /data/Twitter\\ dataset/geoTwitter20-01-01.zip | jq '.place.country_code' | sort | uniq -c | sort -n | tail -n10 | gnuplot -c boxplot.gp top10.png\n```\nFor long commands like this, it is common to break them up onto multiple lines.\nIn the shell (and most programming environments), any line that ends with a backslash `\\` is treated as continuing on to the next line.\n\n\u003e **ASIDE:**\n\u003e Why does `\\` at the end of the line behave this way?\n\u003e Recall that `\\` is used to *escape* the following character so that it does not have it's normal meaning.\n\u003e For example, the python command `a = \"\\\"\"` escapes the innermost `\"` character so that we are able to assign `\"` to the variable `a` rather than close the string.\n\u003e When used at the end of a line, `\\` will escape the newline character `\\n` that immediately follows it.\n\u003e This changes the behavior from being a newline to a standard space character,\n\u003e which causes the command to continue onto the next line.\n\nThus the following more readable shell command is 100% equivalent to the shell command above.\n```\n$ unzip -p /data/Twitter\\ dataset/geoTwitter20-01-01.zip \\\n| jq '.place.country_code' \\\n| sort \\\n| uniq -c \\\n| sort -n \\\n| tail -n10 \\\n| gnuplot -c boxplot.gp top10.png\n```\nThis command takes about 5 minutes to run.\nPerhaps surprisingly, the bottleneck of this command is the `jq` command which parses the JSON.\nTo verify this, press `^Z` while the program above is running,\nthen run the command `ps`.\nYou should get output similar to\n```\n$ ps\n  PID TTY          TIME CMD\n10327 pts/3    00:00:00 bash\n25755 pts/3    00:00:22 unzip\n25756 pts/3    00:00:54 jq\n25757 pts/3    00:00:00 sort\n25758 pts/3    00:00:00 uniq\n25759 pts/3    00:00:00 sort\n25760 pts/3    00:00:00 tail\n25761 pts/3    00:00:00 gnuplot\n25825 pts/3    00:00:00 ps\n```\nRecall that `ps` lists all of the processes that are currently running in the current shell session.\nNotice that each of the commands in your shell 1-liner is actually running concurrently.\nThe time that is listed for each of these processes is the total amount of CPU time that process has used.\nIn the output above, `unzip` has used 22 seconds, and `jq` has used 54 seconds.\nFortunately, because the lambda server has so many CPUs available, each of these processes will be running on their own CPU and running in parallel.\nThe amount of time for the entire command to complete is therefore only the length of time for the slowest command,\nand not the total length of time for all commands.\n\n\u003e **Aside:**\n\u003e If we were to rewrite the above pipeline in pure Python, it would be impossible to have the unzipping and json decoding happen in parallel due to the [Global Interpreter Lock (GIL)](https://wiki.python.org/moin/GlobalInterpreterLock).\n\u003e This GIL is one of the performance warts of python that the [Benevolent Dictator for Life (BDFL)](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) Guido van Rossum implemented in the early days of python to make programming easier.\n\u003e Recently, there has been work on removing the GIL from python in order to make parallel programming possible.\n\u003e [PEP703](https://peps.python.org/pep-0703/) was created in 2023 to address this concern, and the latest versions of python can be run without the GIL.\n\u003e But these changes are still considered experimental and not production ready.\n\u003e\n\u003e The shell, in contrast, has made it trivial to have these expensive tasks run in parallel since the 1970s.\n\nWhen your command completes, upload the `top10.png` file to github.\nYou should see it appear below.\n\n\u003cimg src=top10.png /\u003e\n\n## Part 5: A Simple MapReduce\n\nRecall from the [MapReduce homework](https://github.com/mikeizbicki/twitter_coronavirus) that MapReduce is a parallel procedure for large scale data analysis.\nIn MapReduce, the \"mappers\" analyze small parts of the dataset in parallel, and then the \"reducers\" combine those results into a final result.\nIn the homework, you will use a combination of python and the shell to perform these tasks.\nIn this lab, we'll see how to perform MapReduce entirely in the shell.\n\nOur goal will be to generate a plot of how many tweets were sent from each country in the first 9 days of 2020.\n\nThe mapper is fairly simple.\nIt's just a count group query like we did in the previous section, but without plotting the results.\nThe following shell code runs these mappers in parallel.\n```\n$ for file in /data/Twitter\\ dataset/geoTwitter20-01-0*.zip; do \nunzip -p \"$file\" \\\n| jq '.place.country_code' \\\n| sort \\\n| uniq -c \\\n| sort -n \\\n\u003e map.$(basename \"$file\").dat \u0026\ndone\n```\n\nThe reducer is more complicated.\nThe code below performs the reduce step by merging all of the  outputs from the map step into a single file.\n```\ncat map.geoTwitter20-01-01.zip.dat | while read line; do\n    country_code=$(echo \"$line\" | sed 's/[^a-zA-Z\"]//g')\n    counts=$(cat map.* | grep \"$country_code\" | sed 's/[^0-9]//g')\n    sum=$(echo $counts | sed 's/ /+/g' | bc)\n    echo \"$sum\" \"$country_code\"\ndone | sort -n \u003e reduce\n```\nThe while loop above reads each line from stdin (i.e. the output of the `cat` command) one at a time, storing it in the `line` variable.\nWe then extract the country code, search all of the map files for that country code, and sum their totals together with the `bc` command (bc stands for basic calculator and is the main tool for doing math in the shell).\nThe final output is re-sorted and stored in the file `reduce`.\n\n\u003e **Exercise:**\n\u003e Run the map and reduce procedures above.\n\u003e Then create a plot of the results in the file `country_code_mapreduce.png`.\n\u003e Upload your plot to github and ensure that it appears below.\n\n\u003cimg src=country_code_mapreduce.png /\u003e\n\n## Part 6: MapReduce Challenge\n\nIn this last section, you will have to write your own MapReduce procedure based on the code above.\nI want you to calculate how many tweets sent from the United States are written in each language over the period Jan 1 to Jan 9 2020.\n\n\u003e **Hint:**\n\u003e You should modify the above commands to filter out tweets that don't come from the US with the `grep` command.\n\u003e You should also modify the command to do a count group by query on the language the tweet was written in.\n\u003e You'll have to examine the JSON objects in order to figure out where this information is stored.\n\nPlot the top 20 languages into the file `uslang-top20.png` and upload it to github.\n\n\u003cimg src=uslang-top20.png /\u003e\n\n## Submission\n\nUpload the url of your forked github repo to sakai.\nIn order to get full credit for the lab,\nyou'll need to have all of the images uploaded to github.\n\nThe lab is worth 4 points.\nPart 6 is worth 2 points, and the rest of the lab the other 2 points.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikeizbicki%2Flab-posix-mapreduce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikeizbicki%2Flab-posix-mapreduce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikeizbicki%2Flab-posix-mapreduce/lists"}