Domanda

Voglio fare questo:

  1. esegui un comando
  2. cattura l'output
  3. seleziona una linea
  4. seleziona una colonna di quella linea

Ad esempio, diciamo che voglio ottenere il nome del comando da un $ PID (si noti che questo è solo un esempio, non sto suggerendo che sia il modo più semplice per ottenere un nome del comando da un ID processo - il mio vero problema è con un altro comando il cui formato di output non posso controllare).

Se eseguo ps ottengo:


  PID TTY          TIME CMD
11383 pts/1    00:00:00 bash
11771 pts/1    00:00:00 ps

Ora faccio ps | egrep 11383 e ottieni

11383 pts/1    00:00:00 bash

Passaggio successivo: ps | egrep 11383 | taglia -d " & Quot; -f 4 . L'output è:

<absolutely nothing/>

Il problema è che cut taglia l'output di singoli spazi e poiché ps aggiunge alcuni spazi tra la 2a e la 3a colonna per mantenere la somiglianza di una tabella, < code> cut seleziona una stringa vuota. Ovviamente, potrei usare cut per selezionare il 7 ° e non il 4 ° campo, ma come posso sapere, specialmente quando l'output è variabile e sconosciuto in anticipo.

È stato utile?

Soluzione

Un modo semplice è aggiungere un passaggio di tr per eliminare eventuali separatori di campo ripetuti:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4

Altri suggerimenti

Penso che il modo più semplice sia usare awk . Esempio:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash

Nota che l'opzione tr -s '' non rimuoverà nessuno spazio iniziale. Se la colonna è allineata a destra (come con ps pid) ...

$ ps h -o pid,user -C ssh,sshd | tr -s " "
 1543 root
19645 root
19731 root

Quindi il taglio comporterà una riga vuota per alcuni di quei campi se è la prima colonna:

$ <previous command> | cut -d ' ' -f1

19645
19731

A meno che tu non lo preceda con uno spazio, ovviamente

$ <command> | sed -e "s/.*/ &/" | tr -s " "

Ora, per questo caso particolare di numeri pid (non nomi), esiste una funzione chiamata pgrep :

$ pgrep ssh


Funzioni shell

Tuttavia, in generale è ancora possibile utilizzare funzioni di shell in modo conciso, poiché il comando read contiene una cosa chiara:

$ <command> | while read a b; do echo $a; done

Il primo parametro da leggere, a , seleziona la prima colonna, e se c'è di più, tutto il resto verrà inserito in b . Di conseguenza, non hai mai bisogno di più variabili del numero della colonna +1 .

Quindi,

while read a b c d; do echo $c; done

genererà quindi la terza colonna. Come indicato nel mio commento ...

Una lettura con pipe verrà eseguita in un ambiente che non passa le variabili allo script chiamante.

out=$(ps whatever | { read a b c d; echo $c; })

arr=($(ps whatever | { read a b c d; echo $c $b; }))
echo ${arr[1]}     # will output 'b'`


La soluzione array

Quindi finiamo con la risposta di @frayser che è usare la variabile shell IFS che per impostazione predefinita è uno spazio, per dividere la stringa in un array. Funziona solo in Bash però. Dash e Ash non lo supportano. Ho avuto davvero delle difficoltà a dividere una stringa in componenti in una cosa di Busybox. È abbastanza facile ottenere un singolo componente (ad es. Usando awk) e quindi ripeterlo per ogni parametro necessario. Ma poi finisci ripetutamente chiamando awk sulla stessa linea o usando ripetutamente un blocco di lettura con eco sulla stessa linea. Che non è efficiente o carino. Quindi finisci per dividere usando $ {name %% *} e così via. Ti fa desiderare alcune abilità di Python perché in effetti lo scripting della shell non è più molto divertente se la metà o più delle funzionalità a cui sei abituato, sono sparite. Ma puoi presumere che anche Python non sarebbe installato su un tale sistema, e non lo era ;-).

try

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done

Simile alla soluzione awk di Brianegge, ecco l'equivalente Perl:

ps | egrep 11383 | perl -lane 'print $F[3]'

-a abilita la modalità autosplit, che popola l'array @F con i dati della colonna.
Usa -F, se i tuoi dati sono delimitati da virgole, piuttosto che spaziati.

Il campo 3 viene stampato poiché Perl inizia a contare da 0 anziché 1

Ottenere la linea corretta (esempio per la linea n. 6) si fa con testa e coda e la parola corretta (parola n. 4) può essere catturata con awk:

command|head -n 6|tail -n 1|awk '{print $4}'

Uso delle variabili dell'array

set $(ps | egrep "^11383 "); echo $4

o

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}

Invece di fare tutte queste greps e roba del genere, ti consiglio di usare le funzionalità ps per cambiare il formato di output.

ps -o cmd= -p 12345

Ottieni la riga cmmand di un processo con il pid specificato e nient'altro.

Questo è conforme a POSIX e può quindi essere considerato portatile.

Il tuo comando

ps | egrep 11383 | cut -d" " -f 4

manca un tr -s per comprimere gli spazi, come spiegato in la sua risposta .

Tuttavia, potresti voler usare awk , poiché gestisce tutte queste azioni in un unico comando:

ps | awk '/11383/ {print $4}'

Stampa la quarta colonna in quelle righe contenenti 11383 . Se vuoi che corrisponda a 11383 se appare all'inizio della riga, allora puoi dire ps | awk '/ ^ 11383 / {print $ 4}' .

set di Bash analizzerà tutti gli output in parametri di posizione.

Ad esempio, con set $ (free -h) , echo $ 7 mostrerà " Mem: "

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top