How can I get the amount of available memory portably across distributions?

MemAvailable is included in /proc/meminfo since version 3.14 of the kernel; it was added by commit 34e431b0a. That's the determining factor in the output variations you show. The commit message indicates how to estimate available memory without MemAvailable:

Currently, the amount of memory that is available for a new workload, without pushing the system into swap, can be estimated from MemFree, Active(file), Inactive(file), and SReclaimable, as well as the "low" watermarks from /proc/zoneinfo.

The low watermarks are the level beneath which the system will swap. So in the absence of MemAvailable you can at least add up the values given for MemFree, Active(file), Inactive(file) and SReclaimable (whichever are present in /proc/meminfo), and subtract the low watermarks from /proc/zoneinfo. The latter also lists the number of free pages per zone, that might be useful as a comparison...

The complete algorithm is given in the patch to meminfo.c and seems reasonably easy to adapt:

  • sum the low watermarks across all zones;
  • take the identified free memory (MemFree);
  • subtract the low watermark (we need to avoid touching that to avoid swapping);
  • add the amount of memory we can use from the page cache (sum of Active(file) and Inactive(file)): that's the amount of memory used by the page cache, minus either half the page cache, or the low watermark, whichever is smaller;
  • add the amount of memory we can reclaim (SReclaimable), following the same algorithm.

So, putting all this together, you can get the memory available for a new process with:

awk -v low=$(grep low /proc/zoneinfo | awk '{k+=$2}END{print k}') \
 '{a[$1]=$2}
  END{ 
   print a["MemFree:"]+a["Active(file):"]+a["Inactive(file):"]+a["SReclaimable:"]-(12*low); 
  }' /proc/meminfo 

While Stephen's answer is perfectly sufficient and errs on the side of caution, I decided to write up the full logic including the minimum comparisons. Information is first read from /proc/meminfo and stored in a variable so that memory details are consistent.

LOW_WATERMARK=$(awk '$1 == "low" {LOW_WATERMARK += $2} END {print LOW_WATERMARK * 4096}' /proc/zoneinfo)

MEMINFO=$(</proc/meminfo)

MEMINFO_MEMFREE=$(echo "${MEMINFO}" | awk '$1 == "MemFree:" {print $2 * 1024}')
MEMINFO_FILE=$(echo "${MEMINFO}" | awk '{MEMINFO[$1]=$2} END {print (MEMINFO["Active(file):"] + MEMINFO["Inactive(file):"]) * 1024}')
MEMINFO_SRECLAIMABLE=$(echo "${MEMINFO}" | awk '$1 == "SReclaimable:" {print $2 * 1024}')

MEMINFO_MEMAVAILABLE=$((
  MEMINFO_MEMFREE - LOW_WATERMARK
  + MEMINFO_FILE - ((MEMINFO_FILE/2) < LOW_WATERMARK ? (MEMINFO_FILE/2) : LOW_WATERMARK)
  + MEMINFO_SRECLAIMABLE - ((MEMINFO_SRECLAIMABLE/2) < LOW_WATERMARK ? (MEMINFO_SRECLAIMABLE/2) : LOW_WATERMARK)
))

if [[ "${MEMINFO_MEMAVAILABLE}" -le 0 ]]
then
  MEMINFO_MEMAVAILABLE=0
fi

Result stored in variable is in bytes.