Testing if stdout is empty in bash
This blog post is more than 8 years old, so the content may be out of date.
So, you want to check if something's in LDAP…ldapsearch, right?
Well, that works fine if you're a human…but if you're trying to script it - say, because you're trying to puppetise something - it's a little harder. Because ldapsearch provides an exit code of 0, irrespective of whether it matches any entries. It only returns a failure exit code if the query syntax is invalid.
I boiled the problem down to:
- Make ldapsearch only provide output if it matches something
- Have bash test for an empty string in stdout
Tweaking ldapsearch to only provide results if it matches
Here's a very basic search, to list the defined LDAP schemas in the config:
ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(objectclass=olcSchemaConfig)'
Here's the start of the results:
SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 # extended LDIF # # LDAPv3 # base <cn=schema,cn=config> with scope subtree # filter: (objectclass=olcSchemaConfig) # requesting: ALL # # schema, config dn: cn=schema,cn=config objectClass: olcSchemaConfig cn: schema olcObjectIdentifier: OLcfg 1.3.6.1.4.1.4203.1.12.2 olcObjectIdentifier: OLcfgAt OLcfg:3 olcObjectIdentifier: OLcfgGlAt OLcfgAt:0 olcObjectIdentifier: OLcfgBkAt OLcfgAt:1 olcObjectIdentifier: OLcfgDbAt OLcfgAt:2 olcObjectIdentifier: OLcfgOvAt OLcfgAt:3 olcObjectIdentifier: OLcfgCtAt OLcfgAt:4 olcObjectIdentifier: OLcfgOc OLcfg:4
First things first: that SASL authentication message (at the start) has to go. Firstly, it's spat out to STDERR, not STDOUT (so we could simply use output redirection: 2>/dev/null).
There is a simpler way though: -Q (SASL quiet mode).
ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(objectclass=olcSchemaConfig)' -Q
# extended LDIF # # LDAPv3 # base <cn=schema,cn=config> with scope subtree # filter: (objectclass=olcSchemaConfig) # requesting: ALL # # schema, config dn: cn=schema,cn=config objectClass: olcSchemaConfig cn: schema olcObjectIdentifier: OLcfg 1.3.6.1.4.1.4203.1.12.2 olcObjectIdentifier: OLcfgAt OLcfg:3 olcObjectIdentifier: OLcfgGlAt OLcfgAt:0 olcObjectIdentifier: OLcfgBkAt OLcfgAt:1 olcObjectIdentifier: OLcfgDbAt OLcfgAt:2 olcObjectIdentifier: OLcfgOvAt OLcfgAt:3 olcObjectIdentifier: OLcfgCtAt OLcfgAt:4 olcObjectIdentifier: OLcfgOc OLcfg:4
Next, let's remove the LDAP version comments. Here are some controlling options - we want -LLL.
-L print responses in LDIFv1 format -LL print responses in LDIF format without comments -LLL print responses in LDIF format without comments and version
ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(objectclass=olcSchemaConfig)' -Q -LLL
dn: cn=schema,cn=config objectClass: olcSchemaConfig cn: schema olcObjectIdentifier: OLcfg 1.3.6.1.4.1.4203.1.12.2 olcObjectIdentifier: OLcfgAt OLcfg:3 olcObjectIdentifier: OLcfgGlAt OLcfgAt:0 olcObjectIdentifier: OLcfgBkAt OLcfgAt:1 olcObjectIdentifier: OLcfgDbAt OLcfgAt:2 olcObjectIdentifier: OLcfgOvAt OLcfgAt:3 olcObjectIdentifier: OLcfgCtAt OLcfgAt:4 olcObjectIdentifier: OLcfgOc OLcfg:4
OK, that's much better - now we're getting just the data.
Say we're looking for something more precise though - for example, we want puppet to check if the nis schema is installed.
Firstly, lets change the query to check for the NIS schema:
ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(&(objectclass=olcSchemaConfig)(cn={*}nis))'
# extended LDIF # # LDAPv3 # base <cn=schema,cn=config> with scope subtree # filter: (&(objectclass=olcSchemaConfig)(cn={*}nis)) # requesting: ALL # # {2}nis, schema, config dn: cn={2}nis,cn=schema,cn=config objectClass: olcSchemaConfig cn: {2}nis olcAttributeTypes: {0}( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; th e common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatc h SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) olcAttributeTypes: {1}( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolut e path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1 466.115.121.1.26 SINGLE-VALUE ) olcAttributeTypes: {2}( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to th e login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.2 6 SINGLE-VALUE )
And with our quiet options:
ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(&(objectclass=olcSchemaConfig)(cn={*}nis))' -Q -LLL
dn: cn={2}nis,cn=schema,cn=config objectClass: olcSchemaConfig cn: {2}nis olcAttributeTypes: {0}( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; th e common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatc h SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) olcAttributeTypes: {1}( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolut e path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1 466.115.121.1.26 SINGLE-VALUE ) olcAttributeTypes: {2}( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to th e login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.2 6 SINGLE-VALUE )
OK, better again. Still a lot of output though - we only want to know if it's there or not. Thankfully there's an option for that.
usage: ldapsearch [options] [filter [attributes...]] where: filter RFC 4515 compliant LDAP search filter attributes whitespace-separated list of attribute descriptions which may include: 1.1 no attributes * all user attributes + all operational attributes
ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(&(objectclass=olcSchemaConfig)(cn={*}nis))' -Q -LLL 1,1
Gives us:
dn: cn={2}nis,cn=schema,cn=config
Great - now we have an ldapsearch command that gives us a 1-liner output.
Testing for a zero-length string in stdout in bash
Typically, you'd use the test command:
# -n test passes if the string length is not 0. test -n "my string here" && echo "String is not empty." # With a variable: foo="some string here." test -n $foo && echo "String is not empty."
Logically therefore, you simply pipe the ldapsearch command to test:
ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(&(objectclass=olcSchemaConfig)(cn={*}nis))' -Q -LLL 1,1 | test -n
BZZZT…Wrong! test doesn't accept input from pipe. Backticks to exec, maybe?
test -n `ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(&(objectclass=olcSchemaConfig)(cn={*}nis))' -Q -LLL 1,1` && echo "NIS schema is present"
bash: test: dn:: unary operator expected
OK,so doesn't like that either. How about the [[ ]] syntax, with backticks?
[[ -n `ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(&(objectclass=olcSchemaConfig)(cn={*}nis))' -Q -LLL 1,1` ]] && echo "NIS schema is present."
Success! Now to go puppetise that :-)
Post-script
Turns out puppet uses sh, not bash - and of course, I should have known that ;-)
Here's what I ended up with:
# Example: touch /etc/ldap_matched if a schema with name $schema_cn is found. exec { 'test': path => "/usr/bin:/usr/sbin:/bin", command => "touch /etc/ldap_matched", onlyif => "search=`ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b 'cn=schema,cn=config' '(&(objectclass=olcSchemaConfig)(cn={*}${schema_cn}))' 1,1` && test -n \"\$search\"", }
Add new comment